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
100 int keepalive_interval
;
102 char udp_address
[UDP_ADDRESS_LEN
];
109 enum rtsp_state state
;
115 uint16_t last_seq_nr
;
120 static void parse_session(char *request_line
, char *session
, unsigned max
, int *timeout
) {
124 tok
= strtok_r(request_line
, ";", &state
);
127 strncpy(session
, tok
, __MIN(strlen(tok
), max
- 1));
129 while ((tok
= strtok_r(NULL
, ";", &state
)) != NULL
) {
130 if (strncmp(tok
, "timeout=", 8) == 0) {
131 *timeout
= atoi(tok
+ 8);
133 *timeout
-= KEEPALIVE_MARGIN
;
134 else if (*timeout
> 0)
140 static int parse_port(char *str
, uint16_t *port
)
143 if (p
< 0 || p
> UINT16_MAX
)
151 static int parse_transport(stream_t
*access
, char *request_line
) {
152 access_sys_t
*sys
= access
->p_sys
;
157 tok
= strtok_r(request_line
, ";", &state
);
158 if (tok
== NULL
|| strncmp(tok
, "RTP/AVP", 7) != 0)
161 tok
= strtok_r(NULL
, ";", &state
);
162 if (tok
== NULL
|| strncmp(tok
, "multicast", 9) != 0)
165 while ((tok
= strtok_r(NULL
, ";", &state
)) != NULL
) {
166 if (strncmp(tok
, "destination=", 12) == 0) {
167 strncpy(sys
->udp_address
, tok
+ 12, __MIN(strlen(tok
+ 12), UDP_ADDRESS_LEN
- 1));
168 } else if (strncmp(tok
, "port=", 5) == 0) {
172 memset(port
, 0x00, 6);
173 strncpy(port
, tok
+ 5, __MIN(strlen(tok
+ 5), 5));
174 if ((end
= strstr(port
, "-")) != NULL
)
176 err
= parse_port(port
, &sys
->udp_port
);
186 * Semi-interruptible net_Gets replacement.
187 * If an interruption is occuring it will fallback to non-interruptible read
188 * with a given timeout before it returns.
190 * interrupted: Informs the caller whether an interrupt occured or not
192 static char *net_readln_timeout(vlc_object_t
*obj
, int fd
, int timeout
, bool *interrupted
)
195 size_t size
= 0, len
= 0;
202 if (unlikely(size
>= (1 << 16)))
205 goto error
; /* put sane buffer size limit */
208 char *newbuf
= realloc(buf
, size
+ 1024);
209 if (unlikely(newbuf
== NULL
))
218 val
= vlc_recv_i11e(fd
, buf
+ len
, size
- len
, MSG_PEEK
);
219 if (val
<= 0 && errno
== EINTR
) {
228 struct pollfd pfd
= {
234 while((ret
= poll(&pfd
, 1, timeout
)) < 0);
238 val
= recv(fd
, buf
+ len
, size
- len
, MSG_PEEK
);
243 char *end
= memchr(buf
+ len
, '\n', val
);
245 val
= (end
+ 1) - (buf
+ len
);
246 if (recv(fd
, buf
+ len
, val
, 0) != val
)
255 if (len
> 0 && buf
[--len
] == '\r')
259 msg_Err(obj
, "read error: %s", vlc_strerror_c(errno
));
264 #define skip_whitespace(x) while(*x == ' ') x++
265 static enum rtsp_result
rtsp_handle(stream_t
*access
, bool *interrupted
) {
266 access_sys_t
*sys
= access
->p_sys
;
269 bool have_header
= false;
270 size_t content_length
= 0;
275 while (!have_header
) {
276 in
= net_readln_timeout((vlc_object_t
*)access
, sys
->tcp_sock
, 5000,
281 if (strncmp(in
, "RTSP/1.0 ", 9) == 0) {
282 rtsp_result
= atoi(in
+ 9);
283 } else if (strncmp(in
, "Content-Base:", 13) == 0) {
284 free(sys
->content_base
);
287 skip_whitespace(val
);
289 sys
->content_base
= strdup(val
);
290 } else if (strncmp(in
, "Content-Length:", 15) == 0) {
292 skip_whitespace(val
);
294 content_length
= atoi(val
);
295 } else if (strncmp("Session:", in
, 8) == 0) {
297 skip_whitespace(val
);
299 parse_session(val
, sys
->session_id
, 64, &sys
->keepalive_interval
);
300 } else if (strncmp("Transport:", in
, 10) == 0) {
302 skip_whitespace(val
);
304 if (parse_transport(access
, val
) != 0) {
305 rtsp_result
= VLC_EGENERIC
;
308 } else if (strncmp("com.ses.streamID:", in
, 17) == 0) {
310 skip_whitespace(val
);
312 sys
->stream_id
= atoi(val
);
313 } else if (in
[0] == '\0') {
320 /* Discard further content */
321 while (content_length
> 0 &&
322 (read
= net_Read(access
, sys
->tcp_sock
, buffer
, __MIN(sizeof(buffer
), content_length
))))
323 content_length
-= read
;
329 static void satip_cleanup_blocks(void *data
)
331 block_t
**input_blocks
= data
;
333 for (size_t i
= 0; i
< VLEN
; i
++)
334 if (input_blocks
[i
] != NULL
)
335 block_Release(input_blocks
[i
]);
339 static int check_rtp_seq(stream_t
*access
, block_t
*block
)
341 access_sys_t
*sys
= access
->p_sys
;
342 uint16_t seq_nr
= block
->p_buffer
[2] << 8 | block
->p_buffer
[3];
344 if (seq_nr
== sys
->last_seq_nr
) {
345 msg_Warn(access
, "Received duplicate packet (seq_nr=%"PRIu16
")", seq_nr
);
347 } else if (seq_nr
< (uint16_t)(sys
->last_seq_nr
+ 1)) {
348 msg_Warn(access
, "Received out of order packet (seq_nr=%"PRIu16
" < %"PRIu16
")",
349 seq_nr
, sys
->last_seq_nr
);
351 } else if (++sys
->last_seq_nr
> 1 && seq_nr
> sys
->last_seq_nr
) {
352 msg_Warn(access
, "Gap in seq_nr (%"PRIu16
" > %"PRIu16
"), probably lost a packet",
353 seq_nr
, sys
->last_seq_nr
);
356 sys
->last_seq_nr
= seq_nr
;
360 static void satip_teardown(void *data
) {
361 stream_t
*access
= data
;
362 access_sys_t
*sys
= access
->p_sys
;
365 if (sys
->tcp_sock
> 0) {
366 if (sys
->session_id
[0] > 0) {
367 char discard_buf
[32];
368 struct pollfd pfd
= {
374 ssize_t len
= asprintf(&msg
, "TEARDOWN %s RTSP/1.0\r\n"
376 "Session: %s\r\n\r\n",
377 sys
->control
, sys
->cseq
++, sys
->session_id
);
381 /* make socket non-blocking, to avoid blocking when output buffer
382 * has not enough space */
384 fcntl(sys
->tcp_sock
, F_SETFL
, fcntl(sys
->tcp_sock
, F_GETFL
) | O_NONBLOCK
);
386 ioctlsocket(sys
->tcp_sock
, FIONBIO
, &(unsigned long){ 1 });
389 for (unsigned sent
= 0; sent
< len
;) {
390 ret
= poll(&pfd
, 1, 5000);
392 msg_Err(access
, "Timed out sending RTSP teardown\n");
397 ret
= send(sys
->tcp_sock
, msg
+ sent
, len
, MSG_NOSIGNAL
);
399 msg_Err(access
, "Failed to send RTSP teardown: %d\n", ret
);
407 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
) {
408 msg_Err(access
, "Failed to teardown RTSP session");
412 /* Some SATIP servers send a few empty extra bytes after TEARDOWN.
413 * Try to read them, to avoid a TCP socket reset */
414 while ((len
= recv(sys
->tcp_sock
, discard_buf
, sizeof(discard_buf
), 0) > 0));
416 /* Extra sleep for compatibility with some satip servers, that
417 * can't handle new sessions right after teardown */
423 #define RECV_TIMEOUT 2 * 1000 * 1000
424 static void *satip_thread(void *data
) {
425 stream_t
*access
= data
;
426 access_sys_t
*sys
= access
->p_sys
;
427 int sock
= sys
->udp_sock
;
428 mtime_t last_recv
= mdate();
430 mtime_t next_keepalive
= mdate() + sys
->keepalive_interval
* 1000 * 1000;
432 struct mmsghdr msgs
[VLEN
];
433 struct iovec iovecs
[VLEN
];
434 block_t
*input_blocks
[VLEN
];
437 for (size_t i
= 0; i
< VLEN
; i
++) {
438 memset(&msgs
[i
], 0, sizeof (msgs
[i
]));
439 msgs
[i
].msg_hdr
.msg_iov
= &iovecs
[i
];
440 msgs
[i
].msg_hdr
.msg_iovlen
= 1;
441 iovecs
[i
].iov_base
= NULL
;
442 iovecs
[i
].iov_len
= RTSP_RECEIVE_BUFFER
;
443 input_blocks
[i
] = NULL
;
452 while (last_recv
> mdate() - RECV_TIMEOUT
) {
454 for (size_t i
= 0; i
< VLEN
; i
++) {
455 if (input_blocks
[i
] != NULL
)
458 input_blocks
[i
] = block_Alloc(RTSP_RECEIVE_BUFFER
);
459 if (unlikely(input_blocks
[i
] == NULL
))
462 iovecs
[i
].iov_base
= input_blocks
[i
]->p_buffer
;
465 vlc_cleanup_push(satip_cleanup_blocks
, input_blocks
);
466 retval
= recvmmsg(sock
, msgs
, VLEN
, MSG_WAITFORONE
, NULL
);
472 for (int i
= 0; i
< retval
; ++i
) {
473 block_t
*block
= input_blocks
[i
];
475 len
= msgs
[i
].msg_len
;
476 if (check_rtp_seq(access
, block
))
479 block
->p_buffer
+= RTP_HEADER_SIZE
;
480 block
->i_buffer
= len
- RTP_HEADER_SIZE
;
481 block_FifoPut(sys
->fifo
, block
);
482 input_blocks
[i
] = NULL
;
485 if (poll(&ufd
, 1, 20) == -1)
488 block_t
*block
= block_Alloc(RTSP_RECEIVE_BUFFER
);
490 msg_Err(access
, "Failed to allocate memory for input buffer");
494 block_cleanup_push(block
);
495 len
= recv(sock
, block
->p_buffer
, RTSP_RECEIVE_BUFFER
, 0);
498 if (len
< RTP_HEADER_SIZE
) {
499 block_Release(block
);
503 if (check_rtp_seq(access
, block
)) {
504 block_Release(block
);
508 block
->p_buffer
+= RTP_HEADER_SIZE
;
509 block
->i_buffer
= len
- RTP_HEADER_SIZE
;
510 block_FifoPut(sys
->fifo
, block
);
513 if (sys
->keepalive_interval
> 0 && mdate() > next_keepalive
) {
514 net_Printf(access
, sys
->tcp_sock
,
515 "OPTIONS %s RTSP/1.0\r\n"
517 "Session: %s\r\n\r\n",
518 sys
->control
, sys
->cseq
++, sys
->session_id
);
519 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
)
520 msg_Warn(access
, "Failed to keepalive RTSP session");
522 next_keepalive
= mdate() + sys
->keepalive_interval
* 1000 * 1000;
527 satip_cleanup_blocks(input_blocks
);
529 msg_Dbg(access
, "timed out waiting for data...");
530 vlc_fifo_Lock(sys
->fifo
);
532 vlc_fifo_Signal(sys
->fifo
);
533 vlc_fifo_Unlock(sys
->fifo
);
538 static block_t
* satip_block(stream_t
*access
, bool *restrict eof
) {
539 access_sys_t
*sys
= access
->p_sys
;
542 vlc_fifo_Lock(sys
->fifo
);
544 while (vlc_fifo_IsEmpty(sys
->fifo
)) {
547 vlc_fifo_Wait(sys
->fifo
);
550 if ((block
= vlc_fifo_DequeueUnlocked(sys
->fifo
)) == NULL
)
553 vlc_fifo_Unlock(sys
->fifo
);
558 static int satip_control(stream_t
*access
, int i_query
, va_list args
) {
564 case STREAM_CAN_CONTROL_PACE
:
565 case STREAM_CAN_SEEK
:
566 case STREAM_CAN_PAUSE
:
567 pb_bool
= va_arg(args
, bool *);
571 case STREAM_GET_PTS_DELAY
:
572 pi_64
= va_arg(args
, int64_t *);
573 *pi_64
= INT64_C(1000) * var_InheritInteger(access
, "live-caching");
583 /* Bind two adjacent free ports, of which the first one is even (for RTP data)
584 * and the second is odd (RTCP). This is a requirement of the satip
586 static int satip_bind_ports(stream_t
*access
)
588 access_sys_t
*sys
= access
->p_sys
;
591 vlc_rand_bytes(&rnd
, 1);
592 sys
->udp_port
= 9000 + (rnd
* 2); /* randomly chosen, even start point */
593 while (sys
->udp_sock
< 0) {
594 sys
->udp_sock
= net_OpenDgram(access
, "0.0.0.0", sys
->udp_port
, NULL
,
596 if (sys
->udp_sock
< 0) {
597 if (sys
->udp_port
== 65534)
604 sys
->rtcp_sock
= net_OpenDgram(access
, "0.0.0.0", sys
->udp_port
+ 1, NULL
,
606 if (sys
->rtcp_sock
< 0) {
607 close(sys
->udp_sock
);
613 if (sys
->udp_sock
< 0) {
614 msg_Err(access
, "Could not open two adjacent ports for RTP and RTCP data");
621 static int satip_open(vlc_object_t
*obj
)
623 stream_t
*access
= (stream_t
*)obj
;
627 bool multicast
= var_InheritBool(access
, "satip-multicast");
629 access
->p_sys
= sys
= vlc_obj_calloc(obj
, 1, sizeof(*sys
));
633 msg_Dbg(access
, "try to open '%s'", access
->psz_url
);
635 char *psz_host
= var_InheritString(access
, "satip-host");
641 /* convert url to lowercase, some famous m3u playlists for satip contain
642 * uppercase parameters while most (all?) satip servers do only understand
643 * parameters matching lowercase spelling as defined in the specification
645 char *psz_lower_url
= strdup(access
->psz_url
);
646 if (psz_lower_url
== NULL
)
649 for (unsigned i
= 0; i
< strlen(psz_lower_url
); i
++)
650 psz_lower_url
[i
] = tolower(psz_lower_url
[i
]);
652 vlc_UrlParse(&url
, psz_lower_url
);
654 url
.i_port
= RTSP_DEFAULT_PORT
;
655 if (psz_host
== NULL
&& url
.psz_host
)
656 psz_host
= strdup(url
.psz_host
);
657 if (psz_host
== NULL
)
660 if (url
.psz_host
== NULL
|| url
.psz_host
[0] == '\0')
662 msg_Dbg(access
, "malformed URL: %s", psz_lower_url
);
666 msg_Dbg(access
, "connect to host '%s'", psz_host
);
667 sys
->tcp_sock
= net_ConnectTCP(access
, psz_host
, url
.i_port
);
668 if (sys
->tcp_sock
< 0) {
669 msg_Err(access
, "Failed to connect to RTSP server %s:%d",
670 psz_host
, url
.i_port
);
673 setsockopt (sys
->tcp_sock
, SOL_SOCKET
, SO_KEEPALIVE
, &(int){ 1 }, sizeof (int));
675 if (asprintf(&sys
->content_base
, "rtsp://%s:%d/", psz_host
,
677 sys
->content_base
= NULL
;
681 sys
->last_seq_nr
= 0;
682 sys
->keepalive_interval
= (KEEPALIVE_INTERVAL
- KEEPALIVE_MARGIN
);
684 vlc_url_t setup_url
= url
;
686 // substitute "sat.ip" if present with an the host IP that was fetched during device discovery
687 if( !strncasecmp( setup_url
.psz_host
, "sat.ip", 6 ) ) {
688 setup_url
.psz_host
= psz_host
;
691 // reverse the satip protocol trick, as SAT>IP believes to be RTSP
692 if( setup_url
.psz_protocol
== NULL
||
693 strncasecmp( setup_url
.psz_protocol
, "satip", 5 ) == 0 )
695 setup_url
.psz_protocol
= (char *)"rtsp";
698 char *psz_setup_url
= vlc_uri_compose(&setup_url
);
699 if( psz_setup_url
== NULL
)
703 net_Printf(access
, sys
->tcp_sock
,
704 "SETUP %s RTSP/1.0\r\n"
706 "Transport: RTP/AVP;multicast\r\n\r\n",
707 psz_setup_url
, sys
->cseq
++);
709 /* open UDP socket to acquire a free port to use */
710 if (satip_bind_ports(access
)) {
715 net_Printf(access
, sys
->tcp_sock
,
716 "SETUP %s RTSP/1.0\r\n"
718 "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n\r\n",
719 psz_setup_url
, sys
->cseq
++, sys
->udp_port
, sys
->udp_port
+ 1);
723 bool interrupted
= false;
724 if (rtsp_handle(access
, &interrupted
) != RTSP_RESULT_OK
) {
725 msg_Err(access
, "Failed to setup RTSP session");
729 if (asprintf(&sys
->control
, "%sstream=%d", sys
->content_base
, sys
->stream_id
) < 0) {
735 msg_Warn(access
, "SETUP was interrupted, abort startup");
739 /* Extra sleep for compatibility with some satip servers, that
740 * can't handle PLAY right after SETUP */
741 if (vlc_msleep_i11e(50000) < 0)
744 /* Open UDP socket for reading if not done */
746 sys
->udp_sock
= net_OpenDgram(access
, sys
->udp_address
, sys
->udp_port
, "", sys
->udp_port
, IPPROTO_UDP
);
747 if (sys
->udp_sock
< 0) {
748 msg_Err(access
, "Failed to open UDP socket for listening.");
752 sys
->rtcp_sock
= net_OpenDgram(access
, sys
->udp_address
, sys
->udp_port
+ 1, "", sys
->udp_port
+ 1, IPPROTO_UDP
);
753 if (sys
->rtcp_sock
< 0) {
754 msg_Err(access
, "Failed to open RTCP socket for listening.");
759 net_Printf(access
, sys
->tcp_sock
,
760 "PLAY %s RTSP/1.0\r\n"
762 "Session: %s\r\n\r\n",
763 sys
->control
, sys
->cseq
++, sys
->session_id
);
765 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
) {
766 msg_Err(access
, "Failed to play RTSP session");
770 sys
->fifo
= block_FifoNew();
772 msg_Err(access
, "Failed to allocate block fifo.");
775 sys
->fifo_size
= var_InheritInteger(access
, "satip-buffer");
777 if (vlc_clone(&sys
->thread
, satip_thread
, access
, VLC_THREAD_PRIORITY_INPUT
)) {
778 msg_Err(access
, "Failed to create worker thread.");
782 access
->pf_control
= satip_control
;
783 access
->pf_block
= satip_block
;
794 satip_teardown(access
);
797 block_FifoRelease(sys
->fifo
);
798 if (sys
->udp_sock
>= 0)
799 net_Close(sys
->udp_sock
);
800 if (sys
->rtcp_sock
>= 0)
801 net_Close(sys
->rtcp_sock
);
802 if (sys
->tcp_sock
>= 0)
803 net_Close(sys
->tcp_sock
);
805 free(sys
->content_base
);
810 static void satip_close(vlc_object_t
*obj
)
812 stream_t
*access
= (stream_t
*)obj
;
813 access_sys_t
*sys
= access
->p_sys
;
815 vlc_cancel(sys
->thread
);
816 vlc_join(sys
->thread
, NULL
);
818 satip_teardown(access
);
820 block_FifoRelease(sys
->fifo
);
821 net_Close(sys
->udp_sock
);
822 net_Close(sys
->rtcp_sock
);
823 net_Close(sys
->tcp_sock
);
824 free(sys
->content_base
);