]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
SAT>IP server: initial RTSP code
authorJaroslav Kysela <perex@perex.cz>
Fri, 13 Feb 2015 20:53:56 +0000 (21:53 +0100)
committerJaroslav Kysela <perex@perex.cz>
Wed, 11 Mar 2015 20:41:12 +0000 (21:41 +0100)
Makefile
src/http.c
src/http.h
src/satip/rtsp.c [new file with mode: 0644]
src/satip/server.c
src/webui/webui.c

index 30fca816f2b1fe794706fa17f0c42d5706556274..55dbf63f63f4a9d21dc9d93eebb4b496834677e1 100644 (file)
--- 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 \
index e6952ac8c86c17c1ec177eb9c638391c7f7b7d88..be0ac00f6e138f97dbee1c48bdb6f087e8aed028 100644 (file)
@@ -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, 
-                "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
-                "<HTML><HEAD>\r\n"
-                "<TITLE>%d %s</TITLE>\r\n"
-                "</HEAD><BODY>\r\n"
-                "<H1>%d %s</H1>\r\n",
-                error, errtxt, error, errtxt);
+    htsbuf_qprintf(&hc->hc_reply,
+                   "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
+                   "<HTML><HEAD>\r\n"
+                   "<TITLE>%d %s</TITLE>\r\n"
+                   "</HEAD><BODY>\r\n"
+                   "<H1>%d %s</H1>\r\n",
+                   error, errtxt, error, errtxt);
 
-  if (error == HTTP_STATUS_UNAUTHORIZED)
-    htsbuf_qprintf(&hc->hc_reply, "<P><A HREF=\"/\">Default Login</A></P>");
+    if (error == HTTP_STATUS_UNAUTHORIZED)
+      htsbuf_qprintf(&hc->hc_reply, "<P><A HREF=\"/\">Default Login</A></P>");
 
-  htsbuf_qprintf(&hc->hc_reply, "</BODY></HTML>\r\n");
+    htsbuf_qprintf(&hc->hc_reply, "</BODY></HTML>\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;
index 74fd77b34fcd32ee0fa74f8ab7e373ae541305e7..30ce1e5f5cef9bf7b1e8fb163cd287535367c1da 100644 (file)
@@ -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 (file)
index 0000000..229d756
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <ctype.h>
+#include <arpa/inet.h>
+#include <openssl/sha.h>
+
+#if defined(PLATFORM_FREEBSD) || ENABLE_ANDROID
+#include <sys/types.h>
+#include <sys/socket.h>
+#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://<myip>[:<myport>]/ */
+  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);
+}
index 3924a39486f6f25e6fa69ea981a64a91a4223f60..aed63d11d5ab2b40a451f93d151baaeef382c19c 100644 (file)
@@ -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);
index 9db3a87ad2b085b31944d5190cbdda1b54c083dc..d04cd821790c43b4a42a9dab4da16d35623632c9 100644 (file)
@@ -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 ?: "", 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));