From: Jaroslav Kysela Date: Fri, 13 Feb 2015 20:53:56 +0000 (+0100) Subject: SAT>IP server: initial RTSP code X-Git-Tag: v4.1~314 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fd1bc1c5f7dc50495b3d6573e5552da9297a664f;p=thirdparty%2Ftvheadend.git SAT>IP server: initial RTSP code --- diff --git a/Makefile b/Makefile index 30fca816f..55dbf63f6 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,8 @@ SRCS-${CONFIG_UPNP} += \ # SATIP Server SRCS-${CONFIG_SATIP_SERVER} += \ - src/satip/server.c + src/satip/server.c \ + src/satip/rtsp.c SRCS += \ src/api.c \ diff --git a/src/http.c b/src/http.c index e6952ac8c..be0ac00f6 100644 --- a/src/http.c +++ b/src/http.c @@ -61,8 +61,6 @@ static struct strtab HTTP_versiontab[] = { { "RTSP/1.0", RTSP_VERSION_1_0 }, }; -static void http_parse_get_args(http_connection_t *hc, char *args); - /** * */ @@ -185,13 +183,16 @@ http_rc2str(int code) switch(code) { case HTTP_STATUS_OK: return "OK"; case HTTP_STATUS_PARTIAL_CONTENT: return "Partial Content"; - case HTTP_STATUS_NOT_FOUND: return "Not found"; - case HTTP_STATUS_UNAUTHORIZED: return "Unauthorized"; - case HTTP_STATUS_BAD_REQUEST: return "Bad request"; case HTTP_STATUS_FOUND: return "Found"; - case HTTP_STATUS_HTTP_VERSION: return "HTTP Version Not Supported"; + case HTTP_STATUS_BAD_REQUEST: return "Bad Request"; + case HTTP_STATUS_UNAUTHORIZED: return "Unauthorized"; + case HTTP_STATUS_NOT_FOUND: return "Not Found"; + case HTTP_STATUS_UNSUPPORTED: return "Unsupported Media Type"; + case HTTP_STATUS_BANDWIDTH: return "Not Enough Bandwidth"; + case HTTP_STATUS_BAD_SESSION: return "Session Not Found"; + case HTTP_STATUS_HTTP_VERSION: return "HTTP/RTSP Version Not Supported"; default: - return "Unknown returncode"; + return "Unknown Code"; break; } } @@ -213,10 +214,12 @@ http_send_header(http_connection_t *hc, int rc, const char *content, int64_t contentlen, const char *encoding, const char *location, int maxage, const char *range, - const char *disposition) + const char *disposition, + http_arg_list_t *args) { struct tm tm0, *tm; htsbuf_queue_t hdrs; + http_arg_t *ra; time_t t; htsbuf_queue_init(&hdrs, 0); @@ -229,7 +232,8 @@ http_send_header(http_connection_t *hc, int rc, const char *content, htsbuf_qprintf(&hdrs, "Server: HTS/tvheadend\r\n"); if(maxage == 0) { - htsbuf_qprintf(&hdrs, "Cache-Control: no-cache\r\n"); + if (hc->hc_version != RTSP_VERSION_1_0) + htsbuf_qprintf(&hdrs, "Cache-Control: no-cache\r\n"); } else { time(&t); @@ -289,6 +293,13 @@ http_send_header(http_connection_t *hc, int rc, const char *content, if (++hc->hc_cseq == 0) hc->hc_cseq = 1; } + if(hc->hc_session) + htsbuf_qprintf(&hdrs, "Session: %s\r\n", hc->hc_session); + + if (args) { + TAILQ_FOREACH(ra, args, link) + htsbuf_qprintf(&hdrs, "%s: %s\r\n", ra->key, ra->val); + } htsbuf_qprintf(&hdrs, "\r\n"); @@ -305,7 +316,7 @@ http_send_reply(http_connection_t *hc, int rc, const char *content, const char *encoding, const char *location, int maxage) { http_send_header(hc, rc, content, hc->hc_reply.hq_size, - encoding, location, maxage, 0, NULL); + encoding, location, maxage, 0, NULL, NULL); if(hc->hc_no_output) return; @@ -328,25 +339,29 @@ http_error(http_connection_t *hc, int error) tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrstr, 50); if (error != HTTP_STATUS_FOUND && error != HTTP_STATUS_MOVED) - tvhlog(error < 400 ? LOG_INFO : LOG_ERR, "HTTP", "%s: %s -- %d", + tvhlog(error < 400 ? LOG_INFO : LOG_ERR, "http", "%s: %s -- %d", addrstr, hc->hc_url, error); - htsbuf_queue_flush(&hc->hc_reply); + if (hc->hc_version != RTSP_VERSION_1_0) { + htsbuf_queue_flush(&hc->hc_reply); - htsbuf_qprintf(&hc->hc_reply, - "\r\n" - "\r\n" - "%d %s\r\n" - "\r\n" - "

%d %s

\r\n", - error, errtxt, error, errtxt); + htsbuf_qprintf(&hc->hc_reply, + "\r\n" + "\r\n" + "%d %s\r\n" + "\r\n" + "

%d %s

\r\n", + error, errtxt, error, errtxt); - if (error == HTTP_STATUS_UNAUTHORIZED) - htsbuf_qprintf(&hc->hc_reply, "

Default Login

"); + if (error == HTTP_STATUS_UNAUTHORIZED) + htsbuf_qprintf(&hc->hc_reply, "

Default Login

"); - htsbuf_qprintf(&hc->hc_reply, "\r\n"); + htsbuf_qprintf(&hc->hc_reply, "\r\n"); - http_send_reply(hc, error, "text/html", NULL, NULL, 0); + http_send_reply(hc, error, "text/html", NULL, NULL, 0); + } else { + http_send_reply(hc, error, NULL, NULL, NULL, 0); + } } @@ -430,7 +445,7 @@ http_access_verify_ticket(http_connection_t *hc) return; char addrstr[50]; tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrstr, 50); - tvhlog(LOG_INFO, "HTTP", "%s: using ticket %s for %s", + tvhlog(LOG_INFO, "http", "%s: using ticket %s for %s", addrstr, ticket_id, hc->hc_url); } @@ -666,6 +681,20 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill) switch(hc->hc_version) { case RTSP_VERSION_1_0: hc->hc_keep_alive = 1; + /* Extract CSeq */ + if((v = http_arg_get(&hc->hc_args, "CSeq")) != NULL) + hc->hc_cseq = strtoll(v, NULL, 10); + else + hc->hc_cseq = 0; + free(hc->hc_session); + if ((v = http_arg_get(&hc->hc_args, "Session")) != NULL) + hc->hc_session = strdup(v); + else + hc->hc_session = NULL; + if(hc->hc_cseq == 0) { + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return -1; + } break; case HTTP_VERSION_1_0: @@ -707,7 +736,7 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill) if (hc->hc_cseq) rval = hc->hc_process(hc, spill); else - rval = -1; + http_error(hc, HTTP_STATUS_HTTP_VERSION); break; case HTTP_VERSION_1_0: @@ -719,6 +748,7 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill) break; } free(hc->hc_representative); + free(hc->hc_session); return rval; } @@ -753,6 +783,25 @@ http_arg_get(struct http_arg_list *list, const char *name) return NULL; } +/** + * Find an argument associated with a connection and remove it + */ +char * +http_arg_get_remove(struct http_arg_list *list, const char *name) +{ + static char __thread buf[128]; + http_arg_t *ra; + TAILQ_FOREACH(ra, list, link) + if(!strcasecmp(ra->key, name)) { + TAILQ_REMOVE(list, ra, link); + strncpy(buf, ra->val, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + return buf; + } + buf[0] = '\0'; + return buf; +} + /** * Set an argument associated with a connection @@ -887,7 +936,7 @@ http_deescape(char *s) /** * Parse arguments of a HTTP GET url, not perfect, but works for us */ -static void +void http_parse_get_args(http_connection_t *hc, char *args) { char *k, *v; @@ -1011,7 +1060,7 @@ http_serve(int fd, void **opaque, struct sockaddr_storage *peer, { http_connection_t hc; - // Note: global_lock held on entry */ + /* Note: global_lock held on entry */ pthread_mutex_unlock(&global_lock); memset(&hc, 0, sizeof(http_connection_t)); *opaque = &hc; @@ -1031,7 +1080,7 @@ http_serve(int fd, void **opaque, struct sockaddr_storage *peer, *opaque = NULL; } -static void +void http_cancel( void *opaque ) { http_connection_t *hc = opaque; diff --git a/src/http.h b/src/http.h index 74fd77b34..30ce1e5f5 100644 --- a/src/http.h +++ b/src/http.h @@ -22,7 +22,7 @@ #include "htsbuf.h" #include "url.h" #include "tvhpoll.h" -#include "access.h" + #include "access.h" struct channel; struct http_path; @@ -74,12 +74,15 @@ typedef struct http_arg { #define HTTP_STATUS_UNSUPPORTED 415 #define HTTP_STATUS_BAD_RANGE 417 #define HTTP_STATUS_EXPECTATION 418 +#define HTTP_STATUS_BANDWIDTH 453 +#define HTTP_STATUS_BAD_SESSION 454 #define HTTP_STATUS_INTERNAL 500 #define HTTP_STATUS_NOT_IMPLEMENTED 501 #define HTTP_STATUS_BAD_GATEWAY 502 #define HTTP_STATUS_SERVICE 503 #define HTTP_STATUS_GATEWAY_TIMEOUT 504 #define HTTP_STATUS_HTTP_VERSION 505 +#define HTTP_STATUS_OP_NOT_SUPPRT 551 typedef enum http_state { HTTP_CON_WAIT_REQUEST, @@ -145,6 +148,7 @@ typedef struct http_connection { int hc_logout_cookie; int hc_shutdown; uint64_t hc_cseq; + char *hc_session; /* Support for HTTP POST */ @@ -168,6 +172,7 @@ static inline void http_arg_init(struct http_arg_list *list) void http_arg_flush(struct http_arg_list *list); char *http_arg_get(struct http_arg_list *list, const char *name); +char *http_arg_get_remove(struct http_arg_list *list, const char *name); void http_arg_set(struct http_arg_list *list, const char *key, const char *val); @@ -185,10 +190,12 @@ void http_redirect(http_connection_t *hc, const char *location, void http_send_header(http_connection_t *hc, int rc, const char *content, int64_t contentlen, const char *encoding, const char *location, int maxage, const char *range, - const char *disposition); + const char *disposition, http_arg_list_t *args); void http_serve_requests(http_connection_t *hc); +void http_cancel(void *opaque); + typedef int (http_callback_t)(http_connection_t *hc, const char *remain, void *opaque); @@ -223,6 +230,8 @@ int http_access_verify_channel(http_connection_t *hc, int mask, void http_deescape(char *s); +void http_parse_get_args(http_connection_t *hc, char *args); + /* * HTTP/RTSP Client */ diff --git a/src/satip/rtsp.c b/src/satip/rtsp.c new file mode 100644 index 000000000..229d75693 --- /dev/null +++ b/src/satip/rtsp.c @@ -0,0 +1,821 @@ +/* + * Tvheadend - SAT-IP server - RTSP part + * + * Copyright (C) 2015 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "input.h" +#include "htsbuf.h" +#include "htsmsg_xml.h" +#include "upnp.h" +#include "http.h" +#include "settings.h" +#include "config.h" +#include "satip/server.h" + +#include +#include +#include + +#if defined(PLATFORM_FREEBSD) || ENABLE_ANDROID +#include +#include +#endif + +#define RTSP_TIMEOUT 30 +#define RTSP_PIDS 128 + +typedef struct session { + TAILQ_ENTRY(session) link; + int delsys; + int stream; + int frontend; + char session[9]; + dvb_mux_conf_t dmc; + int16_t pids[RTSP_PIDS]; + gtimer_t timer; +} session_t; + +static uint32_t session_number; +static char *rtsp_ip = NULL; +static int rtsp_port = -1; +static void *rtsp_server = NULL; +static TAILQ_HEAD(,session) rtsp_sessions; +static pthread_mutex_t rtsp_lock; + +static void rtsp_close_session(session_t *rs); +static void rtsp_free_session(session_t *rs); + + +/* + * + */ +static int +rtsp_delsys(int fe) +{ + int i; + + if (fe < 1) + return DVB_SYS_NONE; + pthread_mutex_lock(&global_lock); + i = config_get_int("satip_dvbt", 0); + if (fe <= i) + return DVB_SYS_DVBT; + fe -= i; + i = config_get_int("satip_dvbs", 0); + if (fe <= i) + return DVB_SYS_DVBS; + fe -= i; + i = config_get_int("satip_dvbc", 0); + if (fe <= i) + return DVB_SYS_DVBC_ANNEX_A; + pthread_mutex_unlock(&global_lock); + return DVB_SYS_NONE; +} + +/* + * + */ +static struct session * +rtsp_new_session(int delsys) +{ + struct session *rs = calloc(1, sizeof(*rs)); + if (rs == NULL) + return NULL; + rs->delsys = delsys; + snprintf(rs->session, sizeof(rs->session), "%08X", session_number); + session_number += 9876; + return rs; +} + +/* + * + */ +static struct session * +rtsp_find_session(http_connection_t *hc) +{ + struct session *rs; + + if (hc->hc_session == NULL) + return NULL; + TAILQ_FOREACH(rs, &rtsp_sessions, link) + if (!strcmp(rs->session, hc->hc_session)) + return rs; + return NULL; +} + +/* + * + */ +static void +rtsp_session_timer_cb(void *aux) +{ + session_t *rs = aux; + + rtsp_close_session(rs); + rtsp_free_session(rs); + tvhwarn("satips", "session %s closed (timeout)", rs->session); +} + +static inline void +rtsp_rearm_session_timer(session_t *rs) +{ + gtimer_arm(&rs->timer, rtsp_session_timer_cb, rs, RTSP_TIMEOUT); +} + +/* + * + */ +static char * +rtsp_check_urlbase(char *u) +{ + char *p; + + /* expect string: rtsp://[:]/ */ + if (u[0] == '\0' || strncmp(u, "rtsp://", 7)) + return NULL; + u += 7; + p = strchr(u, '/'); + if (p == NULL) + return NULL; + *p = '\0'; + if ((p = strchr(u, ':')) != NULL) { + *p = '\0'; + if (atoi(p + 1) != rtsp_port) + return NULL; + } else { + if (rtsp_port != 554) + return NULL; + } + if (strcmp(u, rtsp_ip)) + return NULL; + return p; +} + +/* + * + */ +static int +rtsp_parse_args(http_connection_t *hc, char *u) +{ + char *s; + int stream = 0; + + if (strncmp(u, "stream=", 7) == 0) { + u += 7; + for (s = 0; isdigit(*s); s++); + if (*s != '?') + return -1; + *s = '\0'; + stream = atoi(u); + u = s + 1; + } else { + if (*u != '?') + return -1; + } + http_parse_get_args(hc, u); + return stream; +} + +/* + * + */ +static int +rtsp_addpids(session_t *rs, int16_t *pids) +{ + int pid, i, j; + + while ((pid = *pids++) >= 0) { + for (i = 0; i < RTSP_PIDS; i++) { + if (rs->pids[i] > pid) { + if (rs->pids[RTSP_PIDS-1] >= 0) + return -1; + for (j = RTSP_PIDS-1; j != i; j--) + rs->pids[j] = rs->pids[j-1]; + rs->pids[i] = pid; + break; + } else if (rs->pids[i] == pid) + break; + } + } + return 0; +} + +/* + * + */ +static int +rtsp_delpids(session_t *rs, int16_t *pids) +{ + int pid, i, j; + + while ((pid = *pids++) >= 0) { + for (i = 0; i < RTSP_PIDS; i++) { + if (rs->pids[i] > pid) + break; + else if (rs->pids[i] == pid) { + for (j = i; rs->pids[j] >= 0 && j + 1 < RTSP_PIDS; j++) + rs->pids[j] = rs->pids[j+1]; + rs->pids[RTSP_PIDS-1] = -1; + break; + } + } + } + return 0; +} + +/* + * + */ +static int +rtsp_start(session_t *rs) +{ + return 0; +} + + +/* + * + */ +static int +rtsp_process_options(http_connection_t *hc) +{ + http_arg_list_t args; + char *u = tvh_strdupa(hc->hc_url); + session_t *rs; + + if ((u = rtsp_check_urlbase(u)) == NULL) + goto error; + if (*u) + goto error; + + pthread_mutex_lock(&rtsp_lock); + rs = rtsp_find_session(hc); + if (rs) + rtsp_rearm_session_timer(rs); + pthread_mutex_unlock(&rtsp_lock); + http_arg_init(&args); + http_arg_set(&args, "Public", "OPTIONS,DESCRIBE,SETUP,PLAY,TEARDOWN"); + http_send_header(hc, HTTP_STATUS_OK, NULL, 0, NULL, NULL, 0, NULL, NULL, &args); + http_arg_flush(&args); + return 0; + +error: + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return 0; +} + +/* + * + */ +static inline int +msys_to_tvh(http_connection_t *hc) +{ + static struct strtab tab[] = { + { "dvbs", DVB_SYS_DVBS }, + { "dvbs2", DVB_SYS_DVBS2 }, + { "dvbt", DVB_SYS_DVBT }, + { "dvbt2", DVB_SYS_DVBT2 }, + { "dvbc", DVB_SYS_DVBC_ANNEX_A }, + { "dvbc2", DVB_SYS_DVBC_ANNEX_C }, + }; + const char *s = http_arg_get_remove(&hc->hc_req_args, "msys"); + return s[0] ? str2val(s, tab) : DVB_SYS_NONE; +} + +static inline int +pol_to_tvh(http_connection_t *hc) +{ + static struct strtab tab[] = { + { "h", DVB_POLARISATION_HORIZONTAL }, + { "v", DVB_POLARISATION_VERTICAL }, + { "l", DVB_POLARISATION_CIRCULAR_LEFT }, + { "r", DVB_POLARISATION_CIRCULAR_RIGHT }, + }; + const char *s = http_arg_get_remove(&hc->hc_req_args, "pol"); + return s[0] ? str2val(s, tab) : -1; +} + +static int +fec_to_tvh(http_connection_t *hc) +{ + switch (atoi(http_arg_get_remove(&hc->hc_req_args, "fec"))) { + case 12: return DVB_FEC_1_2; + case 13: return DVB_FEC_1_3; + case 15: return DVB_FEC_1_5; + case 23: return DVB_FEC_2_3; + case 25: return DVB_FEC_2_5; + case 29: return DVB_FEC_2_9; + case 34: return DVB_FEC_3_4; + case 35: return DVB_FEC_3_5; + case 45: return DVB_FEC_4_5; + case 415: return DVB_FEC_4_15; + case 56: return DVB_FEC_5_6; + case 59: return DVB_FEC_5_9; + case 67: return DVB_FEC_6_7; + case 78: return DVB_FEC_7_8; + case 79: return DVB_FEC_7_9; + case 715: return DVB_FEC_7_15; + case 89: return DVB_FEC_8_9; + case 815: return DVB_FEC_8_15; + case 910: return DVB_FEC_9_10; + case 920: return DVB_FEC_9_20; + default: return DVB_FEC_NONE; + } +} + +static int +bw_to_tvh(http_connection_t *hc) +{ + int bw = atof(http_arg_get_remove(&hc->hc_req_args, "bw")) * 1000; + switch (bw) { + case DVB_BANDWIDTH_1_712_MHZ: + case DVB_BANDWIDTH_5_MHZ: + case DVB_BANDWIDTH_6_MHZ: + case DVB_BANDWIDTH_7_MHZ: + case DVB_BANDWIDTH_8_MHZ: + case DVB_BANDWIDTH_10_MHZ: + return bw; + default: + return DVB_BANDWIDTH_NONE; + } +} + +static int +rolloff_to_tvh(http_connection_t *hc) +{ + int ro = atof(http_arg_get_remove(&hc->hc_req_args, "ro")) * 1000; + switch (ro) { + case 0: + return DVB_ROLLOFF_35; + case DVB_ROLLOFF_20: + case DVB_ROLLOFF_25: + case DVB_ROLLOFF_35: + return ro; + default: + return DVB_ROLLOFF_NONE; + } +} + +static int +pilot_to_tvh(http_connection_t *hc) +{ + const char *s = http_arg_get_remove(&hc->hc_req_args, "plts"); + if (strcmp(s, "on") == 0) + return DVB_PILOT_ON; + if (strcmp(s, "off") == 0) + return DVB_PILOT_OFF; + if (s[0] == '\0') + return DVB_PILOT_AUTO; + return DVB_ROLLOFF_NONE; +} + +static int +tmode_to_tvh(http_connection_t *hc) +{ + static struct strtab tab[] = { + { "1k", DVB_TRANSMISSION_MODE_1K }, + { "2k", DVB_TRANSMISSION_MODE_2K }, + { "4k", DVB_TRANSMISSION_MODE_4K }, + { "8k", DVB_TRANSMISSION_MODE_8K }, + { "16k", DVB_TRANSMISSION_MODE_16K }, + { "32k", DVB_TRANSMISSION_MODE_32K }, + }; + const char *s = http_arg_get_remove(&hc->hc_req_args, "tmode"); + if (s[0]) { + int v = str2val(s, tab); + return v >= 0 ? v : DVB_TRANSMISSION_MODE_NONE; + } + return DVB_TRANSMISSION_MODE_AUTO; +} + +static int +mtype_to_tvh(http_connection_t *hc) +{ + static struct strtab tab[] = { + { "qpsk", DVB_MOD_QPSK }, + { "8psk", DVB_MOD_PSK_8 }, + { "16qam", DVB_MOD_QAM_16 }, + { "32qam", DVB_MOD_QAM_32 }, + { "64qam", DVB_MOD_QAM_64 }, + { "128qam", DVB_MOD_QAM_128 }, + { "256qam", DVB_MOD_QAM_256 }, + }; + const char *s = http_arg_get_remove(&hc->hc_req_args, "mtype"); + if (s[0]) { + int v = str2val(s, tab); + return v >= 0 ? v : DVB_MOD_NONE; + } + return DVB_MOD_AUTO; +} + +static int +gi_to_tvh(http_connection_t *hc) +{ + switch (atoi(http_arg_get_remove(&hc->hc_req_args, "gi"))) { + case 0: return DVB_GUARD_INTERVAL_AUTO; + case 14: return DVB_GUARD_INTERVAL_1_4; + case 18: return DVB_GUARD_INTERVAL_1_8; + case 116: return DVB_GUARD_INTERVAL_1_16; + case 132: return DVB_GUARD_INTERVAL_1_32; + case 1128: return DVB_GUARD_INTERVAL_1_128; + case 19128: return DVB_GUARD_INTERVAL_19_128; + case 19256: return DVB_GUARD_INTERVAL_19_256; + default: return DVB_GUARD_INTERVAL_NONE; + } +} + +static int +parse_pids(char *p, int16_t *pids) +{ + char *x, *saveptr; + int i = 0; + + if (p == '\0') { + pids[0] = -1; + return 0; + } + x = strtok_r(p, ",", &saveptr); + while (1) { + if (x == NULL) + break; + if (i >= RTSP_PIDS) + return -1; + pids[i] = atoi(x); + if (pids[i] < 0 || pids[i] > 8191) + return -1; + x = strtok_r(NULL, ",", &saveptr); + } + if (i == 0) + return -1; + pids[i] = -1; + return 0; +} + +/* + * + */ +static int +rtsp_process_play(http_connection_t *hc, int setup) +{ + session_t *rs; + int errcode = HTTP_STATUS_BAD_REQUEST; + int stream, delsys = DVB_SYS_NONE, msys, fe, src, freq, pol, sr; + int fec, ro, plts, bw, tmode, mtype, gi, plp, t2id, sm, c2tft, ds, specinv; + char *u, *s; + char *pids, *addpids, *delpids; + int16_t _pids[RTSP_PIDS+1], _addpids[RTSP_PIDS+1], _delpids[RTSP_PIDS+1]; + dvb_mux_conf_t *dmc; + + u = tvh_strdupa(hc->hc_url); + if ((u = rtsp_check_urlbase(u)) == NULL || + (stream = rtsp_parse_args(hc, u)) < 0) + goto error2; + + fe = atoi(http_arg_get_remove(&hc->hc_req_args, "fe")); + addpids = http_arg_get_remove(&hc->hc_req_args, "addpids"); + if (parse_pids(addpids, _addpids)) goto error2; + delpids = http_arg_get_remove(&hc->hc_req_args, "delpids"); + if (parse_pids(delpids, _delpids)) goto error2; + msys = msys_to_tvh(hc); + if (msys < 0) + goto error2; + + if (addpids || delpids) { + if (setup) + goto error2; + if (!stream) + goto error2; + } + + pthread_mutex_lock(&rtsp_lock); + + rs = rtsp_find_session(hc); + + if (fe > 0) { + delsys = rtsp_delsys(fe); + if (delsys == DVB_SYS_NONE) + goto error; + } + + if (setup) { + if (msys == DVB_SYS_NONE) + goto error; + if (!rs) + rs = rtsp_new_session(msys); + else + rtsp_close_session(rs); + } else { + if (!rs || stream != rs->stream) { + if (rs) + errcode = HTTP_STATUS_NOT_FOUND; + goto error; + } + } + + if (!setup && rs->frontend == fe && TAILQ_EMPTY(&hc->hc_req_args)) + goto play; + + dmc = &rs->dmc; + dvb_mux_conf_init(dmc, msys); + rs->frontend = fe; + + pids = http_arg_get_remove(&hc->hc_req_args, "pids"); + if (parse_pids(pids, _pids)) goto error; + freq = atof(http_arg_get_remove(&hc->hc_req_args, "freq")) * 1000; + if (freq < 1000) goto error; + mtype = mtype_to_tvh(hc); + if (mtype == DVB_MOD_NONE) goto error; + + if (msys == DVB_SYS_DVBS || msys == DVB_SYS_DVBS2) { + + src = atoi(http_arg_get_remove(&hc->hc_req_args, "src")); + if (src < 1) goto error; + pol = pol_to_tvh(hc); + if (pol < 0) goto error; + sr = atof(http_arg_get_remove(&hc->hc_req_args, "sr")) * 1000; + if (sr < 1000) goto error; + fec = fec_to_tvh(hc); + if (fec == DVB_FEC_NONE) goto error; + ro = rolloff_to_tvh(hc); + if (ro == DVB_ROLLOFF_NONE || + (ro != DVB_ROLLOFF_35 && msys == DVB_SYS_DVBS)) goto error; + plts = pilot_to_tvh(hc); + if (plts == DVB_PILOT_NONE) goto error; + + if (!TAILQ_EMPTY(&hc->hc_req_args)) + goto error; + + dmc->dmc_fe_rolloff = ro; + dmc->dmc_fe_pilot = plts; + dmc->u.dmc_fe_qpsk.polarisation = pol; + dmc->u.dmc_fe_qpsk.symbol_rate = sr; + dmc->u.dmc_fe_qpsk.fec_inner = fec; + + } else if (msys == DVB_SYS_DVBT || msys == DVB_SYS_DVBT2) { + + bw = bw_to_tvh(hc); + if (bw == DVB_BANDWIDTH_NONE) goto error; + tmode = tmode_to_tvh(hc); + if (tmode == DVB_TRANSMISSION_MODE_NONE) goto error; + gi = gi_to_tvh(hc); + if (gi == DVB_GUARD_INTERVAL_NONE) goto error; + fec = fec_to_tvh(hc); + if (fec == DVB_FEC_NONE) goto error; + plp = atoi(http_arg_get_remove(&hc->hc_req_args, "plp")); + if (plp < 0 || plp > 255) goto error; + s = http_arg_get_remove(&hc->hc_req_args, "t2id"); + t2id = s[0] ? atoi(s) : DVB_NO_STREAM_ID_FILTER; + if (t2id < 0 || t2id > 65535) goto error; + sm = atoi(http_arg_get_remove(&hc->hc_req_args, "sm")); + if (sm < 0 || sm > 1) goto error; + + if (!TAILQ_EMPTY(&hc->hc_req_args)) + goto error; + + dmc->u.dmc_fe_ofdm.bandwidth = bw; + dmc->u.dmc_fe_ofdm.code_rate_HP = fec; + dmc->u.dmc_fe_ofdm.code_rate_LP = DVB_FEC_NONE; + dmc->u.dmc_fe_ofdm.transmission_mode = tmode; + dmc->u.dmc_fe_ofdm.guard_interval = gi; + dmc->u.dmc_fe_ofdm.hierarchy_information = DVB_HIERARCHY_AUTO; + dmc->dmc_fe_stream_id = plp; + dmc->dmc_fe_pls_code = t2id; + dmc->dmc_fe_pls_mode = sm; /* check */ + + } else if (msys == DVB_SYS_DVBC_ANNEX_A || msys == DVB_SYS_DVBC_ANNEX_C) { + + c2tft = atoi(http_arg_get_remove(&hc->hc_req_args, "c2tft")); + if (c2tft < 0 || c2tft > 2) goto error; + bw = bw_to_tvh(hc); + if (bw == DVB_BANDWIDTH_NONE) goto error; + sr = atof(http_arg_get_remove(&hc->hc_req_args, "sr")) * 1000; + if (sr < 1000) goto error; + ds = atoi(http_arg_get_remove(&hc->hc_req_args, "ds")); + if (ds < 0 || ds > 255) goto error; + plp = atoi(http_arg_get_remove(&hc->hc_req_args, "plp")); + if (plp < 0 || plp > 255) goto error; + specinv = atoi(http_arg_get_remove(&hc->hc_req_args, "specinv")); + if (specinv < 0 || specinv > 1) goto error; + + if (!TAILQ_EMPTY(&hc->hc_req_args)) + goto error; + + dmc->u.dmc_fe_qpsk.symbol_rate = sr; + dmc->u.dmc_fe_qpsk.fec_inner = DVB_FEC_NONE; + dmc->dmc_fe_inversion = specinv; + dmc->dmc_fe_stream_id = plp; + dmc->dmc_fe_pls_code = ds; /* check */ + + } else { + + goto error; + + } + + dmc->dmc_fe_freq = freq; + dmc->dmc_fe_modulation = mtype; + + if (setup) { + if (pids) + rtsp_addpids(rs, _pids); + goto end; + } + +play: + if (delpids) + rtsp_delpids(rs, _delpids); + if (addpids) + rtsp_addpids(rs, _addpids); + if (rtsp_start(rs) < 0) { + errcode = HTTP_STATUS_SERVICE;; + goto error; + } + +end: + pthread_mutex_unlock(&rtsp_lock); + return 0; + +error: + pthread_mutex_unlock(&rtsp_lock); +error2: + http_error(hc, errcode); + return 0; +} + +/* + * + */ +static int +rtsp_process_teardown(http_connection_t *hc) +{ + char *u = tvh_strdupa(hc->hc_url); + struct session *rs = NULL; + int stream; + + if ((u = rtsp_check_urlbase(u)) == NULL || + (stream = rtsp_parse_args(hc, u)) < 0) { + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return 0; + } + + pthread_mutex_lock(&rtsp_lock); + rs = rtsp_find_session(hc); + if (!rs || stream != rs->stream) { + pthread_mutex_unlock(&rtsp_lock); + http_error(hc, !rs ? HTTP_STATUS_BAD_SESSION : HTTP_STATUS_NOT_FOUND); + } else { + rtsp_close_session(rs); + pthread_mutex_unlock(&rtsp_lock); + rtsp_free_session(rs); + http_send_header(hc, HTTP_STATUS_OK, NULL, 0, NULL, NULL, 0, NULL, NULL, NULL); + } + return 0; +} + +/** + * Process a RTSP request + */ +static int +rtsp_process_request(http_connection_t *hc, htsbuf_queue_t *spill) +{ + switch (hc->hc_cmd) { + case RTSP_CMD_OPTIONS: + return rtsp_process_options(hc); + case RTSP_CMD_SETUP: + case RTSP_CMD_PLAY: + return rtsp_process_play(hc, hc->hc_cmd == RTSP_CMD_SETUP); + case RTSP_CMD_TEARDOWN: + return rtsp_process_teardown(hc); + default: + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return 0; + } +} + +/* + * + */ +static void +rtsp_serve(int fd, void **opaque, struct sockaddr_storage *peer, + struct sockaddr_storage *self) +{ + http_connection_t hc; + + memset(&hc, 0, sizeof(http_connection_t)); + *opaque = &hc; + /* Note: global_lock held on entry */ + pthread_mutex_unlock(&global_lock); + + hc.hc_fd = fd; + hc.hc_peer = peer; + hc.hc_self = self; + hc.hc_process = rtsp_process_request; + hc.hc_cseq = 1; + + http_serve_requests(&hc); + + close(fd); + + /* Note: leave global_lock held for parent */ + pthread_mutex_lock(&global_lock); + *opaque = NULL; +} + +/* + * + */ +static void +rtsp_close_session(session_t *rs) +{ + gtimer_disarm(&rs->timer); +} + +/* + * + */ +static void +rtsp_free_session(session_t *rs) +{ + gtimer_disarm(&rs->timer); + TAILQ_REMOVE(&rtsp_sessions, rs, link); + free(rs); +} + +/* + * + */ +static void +rtsp_close_sessions(void) +{ + session_t *rs; + while ((rs = TAILQ_FIRST(&rtsp_sessions)) != NULL) { + rtsp_close_session(rs); + rtsp_free_session(rs); + } +} + +/* + * + */ +void satip_server_rtsp_init(const char *bindaddr, int port) +{ + static tcp_server_ops_t ops = { + .start = rtsp_serve, + .stop = NULL, + .cancel = http_cancel + }; + int reg = 0; + uint8_t rnd[4]; + if (!rtsp_server) { + uuid_random(rnd, sizeof(rnd)); + session_number = *(uint32_t *)rnd; + TAILQ_INIT(&rtsp_sessions); + pthread_mutex_init(&rtsp_lock, NULL); + } + if (rtsp_port != port && rtsp_server) { + pthread_mutex_lock(&rtsp_lock); + rtsp_close_sessions(); + pthread_mutex_unlock(&rtsp_lock); + tcp_server_delete(rtsp_server); + reg = 1; + } + free(rtsp_ip); + rtsp_ip = strdup(bindaddr); + rtsp_port = port; + rtsp_server = tcp_server_create(bindaddr, port, &ops, NULL); + if (reg) + tcp_server_register(rtsp_server); +} + +void satip_server_rtsp_register(void) +{ + tcp_server_register(rtsp_server); +} + +void satip_server_rtsp_done(void) +{ + pthread_mutex_lock(&global_lock); + rtsp_close_sessions(); + if (rtsp_server) + tcp_server_delete(rtsp_server); + rtsp_server = NULL; + rtsp_port = -1; + free(rtsp_ip); + rtsp_ip = NULL; + pthread_mutex_unlock(&global_lock); +} diff --git a/src/satip/server.c b/src/satip/server.c index 3924a3948..aed63d11d 100644 --- a/src/satip/server.c +++ b/src/satip/server.c @@ -173,7 +173,7 @@ satip_server_http_xml(http_connection_t *hc) free(devicelist); - http_send_header(hc, 200, "text/xml", strlen(buf), 0, NULL, 10, 0, NULL); + http_send_header(hc, 200, "text/xml", strlen(buf), 0, NULL, 10, 0, NULL, NULL); tvh_write(hc->hc_fd, buf, strlen(buf)); return 0; @@ -474,7 +474,8 @@ void satip_server_config_changed(void) if (!satip_server_rtsp_port_locked) { rtsp_port = config_get_int("satip_rtsp", 0); satip_server_rtsp_port = rtsp_port; - if (rtsp_port <= 0) { + if (rtsp_port > 0) { + satip_server_rtsp_init(http_server_ip, rtsp_port); tvhinfo("satips", "SAT>IP Server reinitialized (HTTP %s:%d, RTSP %s:%d, DVB-T %d, DVB-S2 %d, DVB-C %d)", http_server_ip, http_server_port, http_server_ip, rtsp_port, config_get_int("satip_dvbt", 0), @@ -483,6 +484,7 @@ void satip_server_config_changed(void) satips_upnp_send_announce(); } else { tvhinfo("satips", "SAT>IP Server shutdown"); + satip_server_rtsp_done(); satips_upnp_send_byebye(); } } @@ -519,8 +521,13 @@ void satip_server_init(int rtsp_port) if (rtsp_port <= 0) return; - tvhinfo("satips", "SAT>IP Server initialized (HTTP %s:%d, RTSP %s:%d)", - http_server_ip, http_server_port, http_server_ip, rtsp_port); + satip_server_rtsp_init(http_server_ip, rtsp_port); + + tvhinfo("satips", "SAT>IP Server initialized (HTTP %s:%d, RTSP %s:%d, DVB-T %d, DVB-S2 %d, DVB-C %d)", + http_server_ip, http_server_port, http_server_ip, rtsp_port, + config_get_int("satip_dvbt", 0), + config_get_int("satip_dvbs", 0), + config_get_int("satip_dvbc", 0)); } void satip_server_register(void) @@ -576,13 +583,16 @@ void satip_server_register(void) satips_upnp_discovery->us_destroy = satips_upnp_discovery_destroy; } + satip_server_rtsp_register(); satips_upnp_send_announce(); } void satip_server_done(void) { + satip_server_rtsp_done(); if (satip_server_rtsp_port > 0) satips_upnp_send_byebye(); + satip_server_rtsp_port = 0; free(http_server_ip); http_server_ip = NULL; free(satip_server_uuid); diff --git a/src/webui/webui.c b/src/webui/webui.c index 9db3a87ad..d04cd8217 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -189,7 +189,7 @@ page_static_file(http_connection_t *hc, const char *remain, void *opaque) size = fb_size(fp); gzip = fb_gzipped(fp) ? "gzip" : NULL; - http_send_header(hc, 200, content, size, gzip, NULL, 10, 0, NULL); + http_send_header(hc, 200, content, size, gzip, NULL, 10, 0, NULL, NULL); while (!fb_eof(fp)) { ssize_t c = fb_read(fp, buf, sizeof(buf)); if (c < 0) { @@ -1002,7 +1002,7 @@ page_xspf(http_connection_t *hc, const char *remain, void *opaque) image ? " " : "", image ?: "", image ? "\r\n" : ""); len = strlen(buf); - http_send_header(hc, 200, "application/xspf+xml", len, 0, NULL, 10, 0, NULL); + http_send_header(hc, 200, "application/xspf+xml", len, 0, NULL, 10, 0, NULL, NULL); tvh_write(hc->hc_fd, buf, len); free(hostpath); @@ -1034,7 +1034,7 @@ page_m3u(http_connection_t *hc, const char *remain, void *opaque) %s/%s%s%s\r\n", title, hostpath, remain, profile ? "?profile=" : "", profile ?: ""); len = strlen(buf); - http_send_header(hc, 200, "audio/x-mpegurl", len, 0, NULL, 10, 0, NULL); + http_send_header(hc, 200, "audio/x-mpegurl", len, 0, NULL, 10, 0, NULL, NULL); tvh_write(hc->hc_fd, buf, len); free(hostpath); @@ -1234,7 +1234,7 @@ page_dvrfile(http_connection_t *hc, const char *remain, void *opaque) http_send_header(hc, range ? HTTP_STATUS_PARTIAL_CONTENT : HTTP_STATUS_OK, content, content_len, NULL, NULL, 10, range ? range_buf : NULL, - disposition[0] ? disposition : NULL); + disposition[0] ? disposition : NULL, NULL); ret = 0; if(!hc->hc_no_output) { @@ -1313,7 +1313,7 @@ page_imagecache(http_connection_t *hc, const char *remain, void *opaque) return HTTP_STATUS_NOT_FOUND; } - http_send_header(hc, 200, NULL, st.st_size, 0, NULL, 10, 0, NULL); + http_send_header(hc, 200, NULL, st.st_size, 0, NULL, 10, 0, NULL, NULL); while (1) { c = read(fd, buf, sizeof(buf));