1 /*****************************************************************************
2 * satip.c: SAT>IP input module
3 *****************************************************************************
4 * Copyright © 2016 VLC authors and VideoLAN
5 * Copyright © 2016 jusst technologies GmbH
6 * Copyright © 2016 Videolabs SAS
7 * Copyright © 2016 Julian Scheel
9 * Authors: Julian Scheel <julian@jusst.de>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_access.h>
37 #include <vlc_network.h>
38 #include <vlc_block.h>
41 #include <vlc_interrupt.h>
50 #define RTSP_DEFAULT_PORT 554
51 #define RTSP_RECEIVE_BUFFER 2048
52 #define RTP_HEADER_SIZE 12
54 #define KEEPALIVE_INTERVAL 60
55 #define KEEPALIVE_MARGIN 5
57 static int satip_open(vlc_object_t
*);
58 static void satip_close(vlc_object_t
*);
60 #define BUFFER_TEXT N_("Receive buffer")
61 #define BUFFER_LONGTEXT N_("UDP receive buffer size (bytes)")
63 #define MULTICAST_TEXT N_("Request multicast stream")
64 #define MULTICAST_LONGTEXT N_("Request server to send stream as multicast")
66 #define SATIP_HOST_TEXT N_("Host")
69 set_shortname("satip")
70 set_description( N_("SAT>IP Receiver Plugin") )
71 set_capability("access", 201)
72 set_callbacks(satip_open
, satip_close
)
73 set_category(CAT_INPUT
)
74 set_subcategory(SUBCAT_INPUT_ACCESS
)
75 add_integer("satip-buffer", 0x400000, BUFFER_TEXT
, BUFFER_LONGTEXT
, true)
76 add_bool("satip-multicast", false, MULTICAST_TEXT
, MULTICAST_LONGTEXT
, true)
77 add_string("satip-host", "", SATIP_HOST_TEXT
, SATIP_HOST_TEXT
, true)
79 add_shortcut("rtsp", "satip")
94 #define UDP_ADDRESS_LEN 16
101 int keepalive_interval
;
103 char udp_address
[UDP_ADDRESS_LEN
];
110 enum rtsp_state state
;
116 uint16_t last_seq_nr
;
122 static void net_Printf(stream_t
*access
, int fd
, const char *fmt
, ...)
129 val
= vasprintf(&str
, fmt
, ap
);
133 net_Write(access
, fd
, str
, val
);
138 static void parse_session(char *request_line
, char *session
, unsigned max
, int *timeout
) {
142 tok
= strtok_r(request_line
, ";", &state
);
145 memcpy(session
, tok
, __MIN(strlen(tok
), max
- 1));
147 while ((tok
= strtok_r(NULL
, ";", &state
)) != NULL
) {
148 if (strncmp(tok
, "timeout=", 8) == 0) {
149 *timeout
= atoi(tok
+ 8);
151 *timeout
-= KEEPALIVE_MARGIN
;
152 else if (*timeout
> 0)
158 static int parse_port(char *str
, uint16_t *port
)
161 if (p
< 0 || p
> UINT16_MAX
)
169 static int parse_transport(stream_t
*access
, char *request_line
) {
170 access_sys_t
*sys
= access
->p_sys
;
175 tok
= strtok_r(request_line
, ";", &state
);
176 if (tok
== NULL
|| strncmp(tok
, "RTP/AVP", 7) != 0)
179 tok
= strtok_r(NULL
, ";", &state
);
180 if (tok
== NULL
|| strncmp(tok
, "multicast", 9) != 0)
183 while ((tok
= strtok_r(NULL
, ";", &state
)) != NULL
) {
184 if (strncmp(tok
, "destination=", 12) == 0) {
185 memcpy(sys
->udp_address
, tok
+ 12, __MIN(strlen(tok
+ 12), UDP_ADDRESS_LEN
- 1));
186 } else if (strncmp(tok
, "port=", 5) == 0) {
190 memset(port
, 0x00, 6);
191 memcpy(port
, tok
+ 5, __MIN(strlen(tok
+ 5), 5));
192 if ((end
= strstr(port
, "-")) != NULL
)
194 err
= parse_port(port
, &sys
->udp_port
);
204 * Semi-interruptible net_Gets replacement.
205 * If an interruption is occuring it will fallback to non-interruptible read
206 * with a given timeout before it returns.
208 * interrupted: Informs the caller whether an interrupt occured or not
210 static char *net_readln_timeout(vlc_object_t
*obj
, int fd
, int timeout
, bool *interrupted
)
213 size_t size
= 0, len
= 0;
220 if (unlikely(size
>= (1 << 16)))
223 goto error
; /* put sane buffer size limit */
226 char *newbuf
= realloc(buf
, size
+ 1024);
227 if (unlikely(newbuf
== NULL
))
236 val
= vlc_recv_i11e(fd
, buf
+ len
, size
- len
, MSG_PEEK
);
237 if (val
<= 0 && errno
== EINTR
) {
246 struct pollfd pfd
= {
252 while((ret
= poll(&pfd
, 1, timeout
)) < 0)
255 val
= recv(fd
, buf
+ len
, size
- len
, MSG_PEEK
);
260 char *end
= memchr(buf
+ len
, '\n', val
);
262 val
= (end
+ 1) - (buf
+ len
);
263 if (recv(fd
, buf
+ len
, val
, 0) != val
)
272 if (len
> 0 && buf
[--len
] == '\r')
276 msg_Err(obj
, "read error: %s", vlc_strerror_c(errno
));
281 #define skip_whitespace(x) while(*x == ' ') x++
282 static enum rtsp_result
rtsp_handle(stream_t
*access
, bool *interrupted
) {
283 access_sys_t
*sys
= access
->p_sys
;
286 bool have_header
= false;
287 size_t content_length
= 0;
292 while (!have_header
) {
293 in
= net_readln_timeout((vlc_object_t
*)access
, sys
->tcp_sock
, 5000,
298 if (strncmp(in
, "RTSP/1.0 ", 9) == 0) {
299 rtsp_result
= atoi(in
+ 9);
300 } else if (strncmp(in
, "Content-Base:", 13) == 0) {
301 free(sys
->content_base
);
304 skip_whitespace(val
);
306 sys
->content_base
= strdup(val
);
307 } else if (strncmp(in
, "Content-Length:", 15) == 0) {
309 skip_whitespace(val
);
311 content_length
= atoi(val
);
312 } else if (strncmp("Session:", in
, 8) == 0) {
314 skip_whitespace(val
);
316 parse_session(val
, sys
->session_id
, 64, &sys
->keepalive_interval
);
317 } else if (strncmp("Transport:", in
, 10) == 0) {
319 skip_whitespace(val
);
321 if (parse_transport(access
, val
) != 0) {
322 rtsp_result
= VLC_EGENERIC
;
325 } else if (strncmp("com.ses.streamID:", in
, 17) == 0) {
327 skip_whitespace(val
);
329 sys
->stream_id
= atoi(val
);
330 } else if (in
[0] == '\0') {
337 /* Discard further content */
338 while (content_length
> 0 &&
339 (read
= net_Read(access
, sys
->tcp_sock
, buffer
, __MIN(sizeof(buffer
), content_length
))))
340 content_length
-= read
;
346 static void satip_cleanup_blocks(void *data
)
348 block_t
**input_blocks
= data
;
350 for (size_t i
= 0; i
< VLEN
; i
++)
351 if (input_blocks
[i
] != NULL
)
352 block_Release(input_blocks
[i
]);
356 static int check_rtp_seq(stream_t
*access
, block_t
*block
)
358 access_sys_t
*sys
= access
->p_sys
;
359 uint16_t seq_nr
= block
->p_buffer
[2] << 8 | block
->p_buffer
[3];
361 if (seq_nr
== sys
->last_seq_nr
) {
362 msg_Warn(access
, "Received duplicate packet (seq_nr=%"PRIu16
")", seq_nr
);
364 } else if (seq_nr
< (uint16_t)(sys
->last_seq_nr
+ 1)) {
365 msg_Warn(access
, "Received out of order packet (seq_nr=%"PRIu16
" < %"PRIu16
")",
366 seq_nr
, sys
->last_seq_nr
);
368 } else if (++sys
->last_seq_nr
> 1 && seq_nr
> sys
->last_seq_nr
) {
369 msg_Warn(access
, "Gap in seq_nr (%"PRIu16
" > %"PRIu16
"), probably lost a packet",
370 seq_nr
, sys
->last_seq_nr
);
373 sys
->last_seq_nr
= seq_nr
;
377 static void satip_teardown(void *data
) {
378 stream_t
*access
= data
;
379 access_sys_t
*sys
= access
->p_sys
;
382 if (sys
->tcp_sock
> 0) {
383 if (sys
->session_id
[0] > 0) {
384 char discard_buf
[32];
385 struct pollfd pfd
= {
391 ssize_t len
= asprintf(&msg
, "TEARDOWN %s RTSP/1.0\r\n"
393 "Session: %s\r\n\r\n",
394 sys
->control
, sys
->cseq
++, sys
->session_id
);
398 /* make socket non-blocking, to avoid blocking when output buffer
399 * has not enough space */
401 fcntl(sys
->tcp_sock
, F_SETFL
, fcntl(sys
->tcp_sock
, F_GETFL
) | O_NONBLOCK
);
403 ioctlsocket(sys
->tcp_sock
, FIONBIO
, &(unsigned long){ 1 });
406 for (unsigned sent
= 0; sent
< len
;) {
407 ret
= poll(&pfd
, 1, 5000);
409 msg_Err(access
, "Timed out sending RTSP teardown\n");
414 ret
= send(sys
->tcp_sock
, msg
+ sent
, len
, MSG_NOSIGNAL
);
416 msg_Err(access
, "Failed to send RTSP teardown: %d\n", ret
);
424 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
) {
425 msg_Err(access
, "Failed to teardown RTSP session");
429 /* Some SATIP servers send a few empty extra bytes after TEARDOWN.
430 * Try to read them, to avoid a TCP socket reset */
431 while (recv(sys
->tcp_sock
, discard_buf
, sizeof(discard_buf
), 0) > 0);
433 /* Extra sleep for compatibility with some satip servers, that
434 * can't handle new sessions right after teardown */
435 vlc_tick_sleep(VLC_TICK_FROM_MS(150));
440 #define RECV_TIMEOUT VLC_TICK_FROM_SEC(2)
441 static void *satip_thread(void *data
) {
442 stream_t
*access
= data
;
443 access_sys_t
*sys
= access
->p_sys
;
444 int sock
= sys
->udp_sock
;
445 vlc_tick_t last_recv
= vlc_tick_now();
447 vlc_tick_t next_keepalive
= vlc_tick_now() + vlc_tick_from_sec(sys
->keepalive_interval
);
449 struct mmsghdr msgs
[VLEN
];
450 struct iovec iovecs
[VLEN
];
451 block_t
*input_blocks
[VLEN
];
454 for (size_t i
= 0; i
< VLEN
; i
++) {
455 memset(&msgs
[i
], 0, sizeof (msgs
[i
]));
456 msgs
[i
].msg_hdr
.msg_iov
= &iovecs
[i
];
457 msgs
[i
].msg_hdr
.msg_iovlen
= 1;
458 iovecs
[i
].iov_base
= NULL
;
459 iovecs
[i
].iov_len
= RTSP_RECEIVE_BUFFER
;
460 input_blocks
[i
] = NULL
;
469 while (last_recv
> vlc_tick_now() - RECV_TIMEOUT
) {
471 for (size_t i
= 0; i
< VLEN
; i
++) {
472 if (input_blocks
[i
] != NULL
)
475 input_blocks
[i
] = block_Alloc(RTSP_RECEIVE_BUFFER
);
476 if (unlikely(input_blocks
[i
] == NULL
))
479 iovecs
[i
].iov_base
= input_blocks
[i
]->p_buffer
;
482 vlc_cleanup_push(satip_cleanup_blocks
, input_blocks
);
483 retval
= recvmmsg(sock
, msgs
, VLEN
, MSG_WAITFORONE
, NULL
);
488 last_recv
= vlc_tick_now();
489 for (int i
= 0; i
< retval
; ++i
) {
490 block_t
*block
= input_blocks
[i
];
492 len
= msgs
[i
].msg_len
;
493 if (check_rtp_seq(access
, block
))
496 block
->p_buffer
+= RTP_HEADER_SIZE
;
497 block
->i_buffer
= len
- RTP_HEADER_SIZE
;
498 block_FifoPut(sys
->fifo
, block
);
499 input_blocks
[i
] = NULL
;
502 if (poll(&ufd
, 1, 20) == -1)
505 block_t
*block
= block_Alloc(RTSP_RECEIVE_BUFFER
);
507 msg_Err(access
, "Failed to allocate memory for input buffer");
511 block_cleanup_push(block
);
512 len
= recv(sock
, block
->p_buffer
, RTSP_RECEIVE_BUFFER
, 0);
515 if (len
< RTP_HEADER_SIZE
) {
516 block_Release(block
);
520 if (check_rtp_seq(access
, block
)) {
521 block_Release(block
);
524 last_recv
= vlc_tick_now();
525 block
->p_buffer
+= RTP_HEADER_SIZE
;
526 block
->i_buffer
= len
- RTP_HEADER_SIZE
;
527 block_FifoPut(sys
->fifo
, block
);
530 if (sys
->keepalive_interval
> 0 && vlc_tick_now() > next_keepalive
) {
531 net_Printf(access
, sys
->tcp_sock
,
532 "OPTIONS %s RTSP/1.0\r\n"
534 "Session: %s\r\n\r\n",
535 sys
->control
, sys
->cseq
++, sys
->session_id
);
536 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
)
537 msg_Warn(access
, "Failed to keepalive RTSP session");
539 next_keepalive
= vlc_tick_now() + vlc_tick_from_sec(sys
->keepalive_interval
);
544 satip_cleanup_blocks(input_blocks
);
546 msg_Dbg(access
, "timed out waiting for data...");
547 vlc_fifo_Lock(sys
->fifo
);
549 vlc_fifo_Signal(sys
->fifo
);
550 vlc_fifo_Unlock(sys
->fifo
);
555 static block_t
* satip_block(stream_t
*access
, bool *restrict eof
) {
556 access_sys_t
*sys
= access
->p_sys
;
559 vlc_fifo_Lock(sys
->fifo
);
561 while (vlc_fifo_IsEmpty(sys
->fifo
)) {
564 vlc_fifo_Wait(sys
->fifo
);
567 if ((block
= vlc_fifo_DequeueUnlocked(sys
->fifo
)) == NULL
)
570 vlc_fifo_Unlock(sys
->fifo
);
575 static int satip_control(stream_t
*access
, int i_query
, va_list args
) {
580 case STREAM_CAN_CONTROL_PACE
:
581 case STREAM_CAN_SEEK
:
582 case STREAM_CAN_PAUSE
:
583 pb_bool
= va_arg(args
, bool *);
587 case STREAM_GET_PTS_DELAY
:
588 *va_arg(args
, vlc_tick_t
*) =
589 VLC_TICK_FROM_MS(var_InheritInteger(access
, "live-caching"));
599 /* Bind two adjacent free ports, of which the first one is even (for RTP data)
600 * and the second is odd (RTCP). This is a requirement of the satip
602 static int satip_bind_ports(stream_t
*access
)
604 access_sys_t
*sys
= access
->p_sys
;
607 vlc_rand_bytes(&rnd
, 1);
608 sys
->udp_port
= 9000 + (rnd
* 2); /* randomly chosen, even start point */
609 while (sys
->udp_sock
< 0) {
610 sys
->udp_sock
= net_OpenDgram(access
, "0.0.0.0", sys
->udp_port
, NULL
,
612 if (sys
->udp_sock
< 0) {
613 if (sys
->udp_port
== 65534)
620 sys
->rtcp_sock
= net_OpenDgram(access
, "0.0.0.0", sys
->udp_port
+ 1, NULL
,
622 if (sys
->rtcp_sock
< 0) {
623 close(sys
->udp_sock
);
629 if (sys
->udp_sock
< 0) {
630 msg_Err(access
, "Could not open two adjacent ports for RTP and RTCP data");
637 static int satip_open(vlc_object_t
*obj
)
639 stream_t
*access
= (stream_t
*)obj
;
643 bool multicast
= var_InheritBool(access
, "satip-multicast");
645 access
->p_sys
= sys
= vlc_obj_calloc(obj
, 1, sizeof(*sys
));
649 msg_Dbg(access
, "try to open '%s'", access
->psz_url
);
651 char *psz_host
= var_InheritString(access
, "satip-host");
657 /* convert url to lowercase, some famous m3u playlists for satip contain
658 * uppercase parameters while most (all?) satip servers do only understand
659 * parameters matching lowercase spelling as defined in the specification
661 char *psz_lower_url
= strdup(access
->psz_url
);
662 if (psz_lower_url
== NULL
)
668 for (unsigned i
= 0; i
< strlen(psz_lower_url
); i
++)
669 psz_lower_url
[i
] = tolower(psz_lower_url
[i
]);
671 vlc_UrlParse(&url
, psz_lower_url
);
673 url
.i_port
= RTSP_DEFAULT_PORT
;
674 if (psz_host
== NULL
&& url
.psz_host
)
675 psz_host
= strdup(url
.psz_host
);
676 if (psz_host
== NULL
)
679 if (url
.psz_host
== NULL
|| url
.psz_host
[0] == '\0')
681 msg_Dbg(access
, "malformed URL: %s", psz_lower_url
);
685 msg_Dbg(access
, "connect to host '%s'", psz_host
);
686 sys
->tcp_sock
= net_Connect(access
, psz_host
, url
.i_port
, SOCK_STREAM
, 0);
687 if (sys
->tcp_sock
< 0) {
688 msg_Err(access
, "Failed to connect to RTSP server %s:%d",
689 psz_host
, url
.i_port
);
692 setsockopt (sys
->tcp_sock
, SOL_SOCKET
, SO_KEEPALIVE
, &(int){ 1 }, sizeof (int));
694 if (asprintf(&sys
->content_base
, "rtsp://%s:%d/", psz_host
,
696 sys
->content_base
= NULL
;
700 sys
->last_seq_nr
= 0;
701 sys
->keepalive_interval
= (KEEPALIVE_INTERVAL
- KEEPALIVE_MARGIN
);
703 vlc_url_t setup_url
= url
;
705 // substitute "sat.ip" if present with an the host IP that was fetched during device discovery
706 if( !strncasecmp( setup_url
.psz_host
, "sat.ip", 6 ) ) {
707 setup_url
.psz_host
= psz_host
;
710 // reverse the satip protocol trick, as SAT>IP believes to be RTSP
711 if( setup_url
.psz_protocol
== NULL
||
712 strncasecmp( setup_url
.psz_protocol
, "satip", 5 ) == 0 )
714 setup_url
.psz_protocol
= (char *)"rtsp";
717 char *psz_setup_url
= vlc_uri_compose(&setup_url
);
718 if( psz_setup_url
== NULL
)
722 net_Printf(access
, sys
->tcp_sock
,
723 "SETUP %s RTSP/1.0\r\n"
725 "Transport: RTP/AVP;multicast\r\n\r\n",
726 psz_setup_url
, sys
->cseq
++);
728 /* open UDP socket to acquire a free port to use */
729 if (satip_bind_ports(access
)) {
734 net_Printf(access
, sys
->tcp_sock
,
735 "SETUP %s RTSP/1.0\r\n"
737 "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n\r\n",
738 psz_setup_url
, sys
->cseq
++, sys
->udp_port
, sys
->udp_port
+ 1);
742 bool interrupted
= false;
743 if (rtsp_handle(access
, &interrupted
) != RTSP_RESULT_OK
) {
744 msg_Err(access
, "Failed to setup RTSP session");
748 if (asprintf(&sys
->control
, "%sstream=%d", sys
->content_base
, sys
->stream_id
) < 0) {
754 msg_Warn(access
, "SETUP was interrupted, abort startup");
758 /* Extra sleep for compatibility with some satip servers, that
759 * can't handle PLAY right after SETUP */
760 if (vlc_msleep_i11e(VLC_TICK_FROM_MS(50)) < 0)
763 /* Open UDP socket for reading if not done */
765 sys
->udp_sock
= net_OpenDgram(access
, sys
->udp_address
, sys
->udp_port
, "", sys
->udp_port
, IPPROTO_UDP
);
766 if (sys
->udp_sock
< 0) {
767 msg_Err(access
, "Failed to open UDP socket for listening.");
771 sys
->rtcp_sock
= net_OpenDgram(access
, sys
->udp_address
, sys
->udp_port
+ 1, "", sys
->udp_port
+ 1, IPPROTO_UDP
);
772 if (sys
->rtcp_sock
< 0) {
773 msg_Err(access
, "Failed to open RTCP socket for listening.");
778 net_Printf(access
, sys
->tcp_sock
,
779 "PLAY %s RTSP/1.0\r\n"
781 "Session: %s\r\n\r\n",
782 sys
->control
, sys
->cseq
++, sys
->session_id
);
784 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
) {
785 msg_Err(access
, "Failed to play RTSP session");
789 sys
->fifo
= block_FifoNew();
791 msg_Err(access
, "Failed to allocate block fifo.");
794 sys
->fifo_size
= var_InheritInteger(access
, "satip-buffer");
796 if (vlc_clone(&sys
->thread
, satip_thread
, access
, VLC_THREAD_PRIORITY_INPUT
)) {
797 msg_Err(access
, "Failed to create worker thread.");
801 access
->pf_control
= satip_control
;
802 access
->pf_block
= satip_block
;
813 satip_teardown(access
);
816 block_FifoRelease(sys
->fifo
);
817 if (sys
->udp_sock
>= 0)
818 net_Close(sys
->udp_sock
);
819 if (sys
->rtcp_sock
>= 0)
820 net_Close(sys
->rtcp_sock
);
821 if (sys
->tcp_sock
>= 0)
822 net_Close(sys
->tcp_sock
);
824 free(sys
->content_base
);
829 static void satip_close(vlc_object_t
*obj
)
831 stream_t
*access
= (stream_t
*)obj
;
832 access_sys_t
*sys
= access
->p_sys
;
834 vlc_cancel(sys
->thread
);
835 vlc_join(sys
->thread
, NULL
);
837 satip_teardown(access
);
839 block_FifoRelease(sys
->fifo
);
840 net_Close(sys
->udp_sock
);
841 net_Close(sys
->rtcp_sock
);
842 net_Close(sys
->tcp_sock
);
843 free(sys
->content_base
);