]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add SAT>IP support (remote network tuners)
authorJaroslav Kysela <perex@perex.cz>
Wed, 9 Apr 2014 17:52:23 +0000 (19:52 +0200)
committerJaroslav Kysela <perex@perex.cz>
Mon, 5 May 2014 20:00:35 +0000 (22:00 +0200)
12 files changed:
Makefile
configure
src/api.h
src/input.h
src/input/mpegts.c
src/input/mpegts/satip/satip.c [new file with mode: 0644]
src/input/mpegts/satip/satip.h [new file with mode: 0644]
src/input/mpegts/satip/satip_frontend.c [new file with mode: 0644]
src/input/mpegts/satip/satip_private.h [new file with mode: 0644]
src/input/mpegts/satip/satip_rtsp.c [new file with mode: 0644]
src/input/mpegts/satip/satip_satconf.c [new file with mode: 0644]
src/main.c

index bfd2da08b2a3ab2e0a9132493e90a472cec32744..e0bb0f6c8733cab28950694e564aa238ed37abde 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -209,6 +209,13 @@ SRCS-${CONFIG_LINUXDVB} += \
         src/input/mpegts/linuxdvb/linuxdvb_rotor.c \
         src/input/mpegts/linuxdvb/linuxdvb_en50494.c
 
+# SATIP
+SRCS-${CONFIG_SATIP_CLIENT} += \
+       src/input/mpegts/satip/satip.c \
+       src/input/mpegts/satip/satip_frontend.c \
+       src/input/mpegts/satip/satip_satconf.c \
+       src/input/mpegts/satip/satip_rtsp.c
+
 # IPTV
 SRCS-${CONFIG_IPTV} += \
        src/input/mpegts/iptv/iptv.c \
index 00d302a7f73c664ae7be92e1c0ef3937477b817f..839e53c9065474bbe50f37d51eff737b4567d46f 100755 (executable)
--- a/configure
+++ b/configure
@@ -19,6 +19,7 @@ OPTIONS=(
   "cwc:yes"
   "v4l:no"
   "linuxdvb:yes"
+  "satip_client:yes"
   "iptv:yes"
   "tsfile:yes"
   "dvbscan:yes"
@@ -174,6 +175,17 @@ if enabled_or_auto curl; then
   fi
 fi
 
+#
+# SAT>IP client
+#
+if enabled_or_auto satip_client; then
+  if enabled curl; then
+    enable upnp
+  else
+    die "SAT>IP client requires curl enabled"
+  fi
+fi
+
 #
 # uriparser
 #
index 629330e8187d08bfca25526ee1762d6839af8a67..6a275732a3ecba4ea4f7c6e15472b8dcb7cc6dc1 100644 (file)
--- a/src/api.h
+++ b/src/api.h
@@ -59,6 +59,7 @@ void api_init               ( void );
 void api_done               ( void );
 void api_idnode_init        ( void );
 void api_input_init         ( void );
+void api_input_satip_init   ( void );
 void api_service_init       ( void );
 void api_channel_init       ( void );
 void api_mpegts_init        ( void );
index 6de228f9c761d88da4b287aefd626eea3f11ae7a..1597236f94bc946c1c3fd8acfb05636c405bd09e 100644 (file)
@@ -128,6 +128,9 @@ void tvh_input_stream_destroy ( tvh_input_stream_t *st );
 #if ENABLE_LINUXDVB
 #include "input/mpegts/linuxdvb.h"
 #endif
+#if ENABLE_SATIP_CLIENT
+#include "input/mpegts/satip/satip.h"
+#endif
 #endif
 
 #endif /* __TVH_INPUT_H__ */
index 8f2a0a5cc4253a5cfc9d0a9750d0691c9e0e8a98..51117107bf19daf072eaeaa6a32cb55e22f7186f 100644 (file)
@@ -51,6 +51,11 @@ mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners )
   linuxdvb_init(linuxdvb_mask);
 #endif
 
+  /* SAT>IP DVB client */
+#if ENABLE_SATIP_CLIENT
+  satip_init();
+#endif
+
   /* Mux schedulers */
 #if ENABLE_MPEGTS
   mpegts_mux_sched_init();
@@ -71,6 +76,9 @@ mpegts_done ( void )
 #if ENABLE_LINUXDVB
   tvhftrace("main", linuxdvb_done);
 #endif
+#if ENABLE_SATIP_CLIENT
+  tvhftrace("main", satip_done);
+#endif
 #if ENABLE_TSFILE
   tvhftrace("main", tsfile_done);
 #endif
diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c
new file mode 100644 (file)
index 0000000..0f2b157
--- /dev/null
@@ -0,0 +1,818 @@
+/*
+ *  Tvheadend - SAT-IP client
+ *
+ *  Copyright (C) 2014 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 "settings.h"
+#include "satip_private.h"
+
+#include <arpa/inet.h>
+#include <openssl/sha.h>
+
+/*
+ * SAT-IP client
+ */
+
+static void
+satip_device_class_save ( idnode_t *in )
+{
+  satip_device_save((satip_device_t *)in);
+}
+
+static idnode_set_t *
+satip_device_class_get_childs ( idnode_t *in )
+{
+  satip_device_t *sd = (satip_device_t *)in;
+  idnode_set_t *is = idnode_set_create();
+  satip_frontend_t *lfe;
+
+  TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link)
+    idnode_set_add(is, &lfe->ti_id, NULL);
+  return is;
+}
+
+static const char *
+satip_device_class_get_title( idnode_t *in )
+{
+  static char buf[256];
+  satip_device_t *sd = (satip_device_t *)in;
+  snprintf(buf, sizeof(buf),
+           "%s - %s", sd->sd_info.friendlyname, sd->sd_info.addr);
+  return buf;
+}
+
+const idclass_t satip_device_class =
+{
+  .ic_class      = "satip_client",
+  .ic_caption    = "SAT>IP Client",
+  .ic_save       = satip_device_class_save,
+  .ic_get_childs = satip_device_class_get_childs,
+  .ic_get_title  = satip_device_class_get_title,
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_BOOL,
+      .id       = "fullmux_ok",
+      .name     = "Full Mux Rx mode supported",
+      .opts     = PO_ADVANCED,
+      .off      = offsetof(satip_device_t, sd_fullmux_ok),
+    },
+    {
+      .type     = PT_INT,
+      .id       = "sigscale",
+      .name     = "Signal scale (240 or 100)",
+      .opts     = PO_ADVANCED,
+      .off      = offsetof(satip_device_t, sd_sig_scale),
+    },
+    {
+      .type     = PT_INT,
+      .id       = "pids_max",
+      .name     = "Maximum PIDs",
+      .opts     = PO_ADVANCED,
+      .off      = offsetof(satip_device_t, sd_pids_max),
+    },
+    {
+      .type     = PT_INT,
+      .id       = "pids_len",
+      .name     = "Maximum length of PIDs",
+      .opts     = PO_ADVANCED,
+      .off      = offsetof(satip_device_t, sd_pids_len),
+    },
+    {
+      .type     = PT_BOOL,
+      .id       = "pids_deladd",
+      .name     = "addpids/delpids supported",
+      .opts     = PO_ADVANCED,
+      .off      = offsetof(satip_device_t, sd_pids_deladd),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "addr",
+      .name     = "IP Address",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.addr),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "device_uuid",
+      .name     = "UUID",
+      .opts     = PO_RDONLY,
+      .off      = offsetof(satip_device_t, sd_info.uuid),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "friendly",
+      .name     = "Friendly Name",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.friendlyname),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "serialnum",
+      .name     = "Serial Number",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.serialnum),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "tunercfg",
+      .name     = "Tuner Configuration",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.tunercfg),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "manufacturer",
+      .name     = "Manufacturer",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.manufacturer),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "manufurl",
+      .name     = "Manufacturer URL",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.manufacturerURL),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "modeldesc",
+      .name     = "Model Description",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.modeldesc),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "modelname",
+      .name     = "Model Name",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.modelname),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "modelnum",
+      .name     = "Model Number",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.modelnum),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "bootid",
+      .name     = "Boot ID",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.bootid),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "configid",
+      .name     = "Config ID",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.configid),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "deviceid",
+      .name     = "Device ID",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.deviceid),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "presentation",
+      .name     = "Presentation",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.presentation),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "location",
+      .name     = "Location",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.location),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "server",
+      .name     = "Server",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.server),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "myaddr",
+      .name     = "Local IP Address",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_device_t, sd_info.myaddr),
+    },
+    {}
+  }
+};
+
+/*
+ * Create entry
+ */
+static void
+satip_device_calc_bin_uuid( uint8_t *uuid, const char *satip_uuid )
+{
+  SHA_CTX sha1;
+
+  SHA1_Init(&sha1);
+  SHA1_Update(&sha1, (void*)satip_uuid, strlen(satip_uuid));
+  SHA1_Final(uuid, &sha1);
+}
+
+static void
+satip_device_calc_uuid( uuid_t *uuid, const char *satip_uuid )
+{
+  uint8_t uuidbin[20];
+
+  satip_device_calc_bin_uuid(uuidbin, satip_uuid);
+  bin2hex(uuid->hex, sizeof(uuid->hex), uuidbin, sizeof(uuidbin));
+}
+
+static void
+satip_device_hack( satip_device_t *sd )
+{
+  if (sd->sd_info.deviceid[0] &&
+      strcmp(sd->sd_info.server, "Linux/1.0 UPnP/1.1 IDL4K/1.0") == 0) {
+    /* AXE Linux distribution - Inverto firmware */
+    /* version V1.13.0.105 and probably less */
+    /* really ugly firmware - soooooo much restrictions */
+    sd->sd_fullmux_ok  = 0;
+    sd->sd_pids_max    = 32;
+    sd->sd_pids_deladd = 0;
+    tvhwarn("satip", "Detected old Inverto firmware V1.13.0.105 and less");
+    tvhwarn("satip", "Upgrade to V1.16.0.120 - http://http://www.inverto.tv/support/ - IDL400s");
+  }
+}
+
+static satip_device_t *
+satip_device_create( satip_device_info_t *info )
+{
+  satip_device_t *sd = calloc(1, sizeof(satip_device_t));
+  uuid_t uuid;
+  htsmsg_t *conf = NULL, *feconf = NULL;
+  char *argv[10];
+  int i, j, n, m, fenum, t2, save = 0;
+  dvb_fe_type_t type;
+
+  satip_device_calc_uuid(&uuid, info->uuid);
+
+  conf = hts_settings_load("input/satip/adapters/%s", uuid.hex);
+
+  /* some sane defaults */
+  sd->sd_fullmux_ok  = 1;
+  sd->sd_pids_len    = 127;
+  sd->sd_pids_max    = 32;
+  sd->sd_pids_deladd = 1;
+  sd->sd_sig_scale   = 240;
+
+  if (!tvh_hardware_create0((tvh_hardware_t*)sd, &satip_device_class,
+                            uuid.hex, conf)) {
+    free(sd);
+    return NULL;
+  }
+
+  TAILQ_INIT(&sd->sd_frontends);
+
+  /* we may check if uuid matches, but the SHA hash should be enough */
+  if (sd->sd_info.uuid)
+    free(sd->sd_info.uuid);
+
+#define ASSIGN(x) sd->sd_info.x = info->x; info->x = NULL
+  ASSIGN(myaddr);
+  ASSIGN(addr);
+  ASSIGN(uuid);
+  ASSIGN(bootid);
+  ASSIGN(configid);
+  ASSIGN(deviceid);
+  ASSIGN(server);
+  ASSIGN(location);
+  ASSIGN(friendlyname);
+  ASSIGN(manufacturer);
+  ASSIGN(manufacturerURL);
+  ASSIGN(modeldesc);
+  ASSIGN(modelname);
+  ASSIGN(modelnum);
+  ASSIGN(serialnum);
+  ASSIGN(presentation);
+  ASSIGN(tunercfg);
+#undef ASSIGN
+
+  /*
+   * device specific hacks
+   */
+  satip_device_hack(sd);
+
+  if (conf)
+    feconf = htsmsg_get_map(conf, "frontends");
+  save = !conf || !feconf;
+
+  n = http_tokenize(sd->sd_info.tunercfg, argv, 10, ',');
+  for (i = 0, fenum = 1; i < n; i++) {
+    type = DVB_TYPE_NONE;
+    t2 = 0;
+    if (strncmp(argv[i], "DVBS2-", 6) == 0) {
+      type = DVB_TYPE_S;
+      m = atoi(argv[i] + 6);
+    } else if (strncmp(argv[i], "DVBT2-", 6) == 0) {
+      type = DVB_TYPE_T;
+      m = atoi(argv[i] + 6);
+      t2 = 1;
+    } else if (strncmp(argv[i], "DVBT-", 5) == 0) {
+      type = DVB_TYPE_T;
+      m = atoi(argv[i] + 5);
+    }
+    if (type == DVB_TYPE_NONE) {
+      tvhlog(LOG_ERR, "satip", "%s: bad tuner type [%s]", sd->sd_info.addr, argv[i]);
+    } else if (m < 0 || m > 32) {
+      tvhlog(LOG_ERR, "satip", "%s: bad tuner count [%s]", sd->sd_info.addr, argv[i]);
+    } else {
+      for (j = 0; j < m; j++)
+        if (satip_frontend_create(feconf, sd, type, t2, fenum))
+          fenum++;
+    }
+  }
+
+  if (save)
+    satip_device_save(sd);
+
+  htsmsg_destroy(conf);
+
+  return sd;
+}
+
+static satip_device_t *
+satip_device_find( const char *satip_uuid )
+{
+  tvh_hardware_t *th;
+  uint8_t binuuid[20];
+
+  satip_device_calc_bin_uuid(binuuid, satip_uuid);
+  TVH_HARDWARE_FOREACH(th) {
+    if (idnode_is_instance(&th->th_id, &satip_device_class) &&
+        memcmp(th->th_id.in_uuid, binuuid, UUID_BIN_SIZE) == 0)
+      return (satip_device_t *)th;
+  }
+  return NULL;
+}
+
+void
+satip_device_save( satip_device_t *sd )
+{
+  satip_frontend_t *lfe;
+  htsmsg_t *m, *l;
+
+  m = htsmsg_create_map();
+  idnode_save(&sd->th_id, m);
+
+  l = htsmsg_create_map();
+  TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link)
+    satip_frontend_save(lfe, l);
+  htsmsg_add_msg(m, "frontends", l);
+
+  hts_settings_save(m, "input/satip/adapters/%s",
+                    idnode_uuid_as_str(&sd->th_id));
+  htsmsg_destroy(m);
+}
+
+void
+satip_device_destroy( satip_device_t *sd )
+{
+  satip_frontend_t *lfe;
+
+  lock_assert(&global_lock);
+
+  while ((lfe = TAILQ_FIRST(&sd->sd_frontends)) != NULL)
+    satip_frontend_delete(lfe);
+
+#define FREEM(x) free(sd->sd_info.x)
+  FREEM(myaddr);
+  FREEM(addr);
+  FREEM(uuid);
+  FREEM(bootid);
+  FREEM(configid);
+  FREEM(deviceid);
+  FREEM(location);
+  FREEM(server);
+  FREEM(friendlyname);
+  FREEM(manufacturer);
+  FREEM(manufacturerURL);
+  FREEM(modeldesc);
+  FREEM(modelname);
+  FREEM(modelnum);
+  FREEM(serialnum);
+  FREEM(presentation);
+  FREEM(tunercfg);
+#undef FREEM
+
+  tvh_hardware_delete((tvh_hardware_t*)sd);
+  free(sd);
+}
+
+/*
+ * Discovery job
+ */
+
+typedef struct satip_discovery {
+  TAILQ_ENTRY(satip_discovery) disc_link;
+  char *myaddr;
+  char *location;
+  char *server;
+  char *uuid;
+  char *bootid;
+  char *configid;
+  char *deviceid;
+  url_t url;
+  http_client_t *http_client;
+  time_t http_start;
+  char *desc;
+} satip_discovery_t;
+
+TAILQ_HEAD(satip_discovery_queue, satip_discovery);
+
+static int satip_discoveries_count;
+static struct satip_discovery_queue satip_discoveries;
+static upnp_service_t *satip_discovery_service;
+static gtimer_t satip_discovery_timer;
+static gtimer_t satip_discovery_timerq;
+
+static void
+satip_discovery_destroy(satip_discovery_t *d, int unlink)
+{
+  if (d == NULL)
+    return;
+  if (unlink) {
+    satip_discoveries_count--;
+    TAILQ_REMOVE(&satip_discoveries, d, disc_link);
+  }
+  if (d->http_client)
+    http_close(d->http_client);
+  free(d->myaddr);
+  free(d->location);
+  free(d->server);
+  free(d->uuid);
+  free(d->bootid);
+  free(d->configid);
+  free(d->deviceid);
+  free(d->desc);
+  free(d);
+}
+
+static satip_discovery_t *
+satip_discovery_find(satip_discovery_t *d)
+{
+  satip_discovery_t *sd;
+
+  TAILQ_FOREACH(sd, &satip_discoveries, disc_link)
+    if (strcmp(sd->uuid, d->uuid) == 0)
+      return sd;
+  return NULL;
+}
+
+static size_t
+satip_discovery_http_data(void *p, void *buf, size_t len)
+{
+  satip_discovery_t *d = p;
+  size_t size;
+  char *s;
+  htsmsg_t *xml = NULL, *tags, *root, *device;
+  const char *friendlyname, *manufacturer, *manufacturerURL, *modeldesc;
+  const char *modelname, *modelnum, *serialnum;
+  const char *presentation, *tunercfg;
+  const char *cs;
+  satip_device_info_t info;
+  char errbuf[100];
+
+  size = d->desc ? strlen(d->desc) : 0;
+  if (len + size > 16384)
+    goto finish;
+  d->desc = realloc(d->desc, size + len + 1);
+  memcpy(d->desc + size, buf, len);
+  size += len;
+  d->desc[size] = '\0';
+
+  s = d->desc + size - 1;
+  while (s != d->desc && *s != '/')
+    s--;
+  if (s != d->desc)
+    s--;
+  if (strncmp(s, "</root>", 7))
+    return len;
+  /* Parse */
+  xml = htsmsg_xml_deserialize(d->desc, errbuf, sizeof(errbuf));
+  d->desc = NULL;
+  if (!xml) {
+    tvhlog(LOG_ERR, "satip_discovery_desc", "htsmsg_xml_deserialize error %s", errbuf);
+    goto finish;
+  }
+  if ((tags         = htsmsg_get_map(xml, "tags")) == NULL)
+    goto finish;
+  if ((root         = htsmsg_get_map(tags, "root")) == NULL)
+    goto finish;
+  if ((device       = htsmsg_get_map(root, "tags")) == NULL)
+    goto finish;
+  if ((device       = htsmsg_get_map(device, "device")) == NULL)
+    goto finish;
+  if ((device       = htsmsg_get_map(device, "tags")) == NULL)
+    goto finish;
+  if ((cs           = htsmsg_xml_get_cdata_str(device, "deviceType")) == NULL)
+    goto finish;
+  if (strcmp(cs, "urn:ses-com:device:SatIPServer:1"))
+    goto finish;
+  if ((friendlyname = htsmsg_xml_get_cdata_str(device, "friendlyName")) == NULL)
+    goto finish;
+  if ((manufacturer = htsmsg_xml_get_cdata_str(device, "manufacturer")) == NULL)
+    goto finish;
+  if ((manufacturerURL = htsmsg_xml_get_cdata_str(device, "manufacturerURL")) == NULL)
+    goto finish;
+  if ((modeldesc    = htsmsg_xml_get_cdata_str(device, "modelDescription")) == NULL)
+    goto finish;
+  if ((modelname    = htsmsg_xml_get_cdata_str(device, "modelName")) == NULL)
+    goto finish;
+  if ((modelnum     = htsmsg_xml_get_cdata_str(device, "modelNumber")) == NULL)
+    goto finish;
+  if ((serialnum    = htsmsg_xml_get_cdata_str(device, "serialNumber")) == NULL)
+    goto finish;
+  if ((presentation = htsmsg_xml_get_cdata_str(device, "presentationURL")) == NULL)
+    goto finish;
+  if ((tunercfg     = htsmsg_xml_get_cdata_str(device, "urn:ses-com:satipX_SATIPCAP")) == NULL)
+    goto finish;
+  info.myaddr = strdup(d->myaddr);
+  info.addr = strdup(d->url.host);
+  info.uuid = strdup(d->uuid);
+  info.bootid = strdup(d->bootid);
+  info.configid = strdup(d->configid);
+  info.deviceid = strdup(d->deviceid);
+  info.location = strdup(d->location);
+  info.server = strdup(d->server);
+  info.friendlyname = strdup(friendlyname);
+  info.manufacturer = strdup(manufacturer);
+  info.manufacturerURL = strdup(manufacturerURL);
+  info.modeldesc = strdup(modeldesc);
+  info.modelname = strdup(modelname);
+  info.modelnum = strdup(modelnum);
+  info.serialnum = strdup(serialnum);
+  info.presentation = strdup(presentation);
+  info.tunercfg = strdup(tunercfg);
+  htsmsg_destroy(xml);
+  xml = NULL;
+  pthread_mutex_lock(&global_lock);
+  if (!satip_device_find(info.uuid))
+    satip_device_create(&info);
+  pthread_mutex_unlock(&global_lock);
+  free(info.myaddr);
+  free(info.location);
+  free(info.server);
+  free(info.addr);
+  free(info.uuid);
+  free(info.bootid);
+  free(info.configid);
+  free(info.deviceid);
+  free(info.friendlyname);
+  free(info.manufacturer);
+  free(info.manufacturerURL);
+  free(info.modeldesc);
+  free(info.modelname);
+  free(info.modelnum);
+  free(info.serialnum);
+  free(info.presentation);
+  free(info.tunercfg);
+finish:
+  htsmsg_destroy(xml);
+  return -EIO;
+}
+
+static void
+satip_discovery_http_fail(void *p)
+{
+  pthread_mutex_lock(&global_lock);
+  satip_discovery_destroy((satip_discovery_t *)p, 1);
+  pthread_mutex_unlock(&global_lock);
+}
+
+static void
+satip_discovery_timerq_cb(void *aux)
+{
+  satip_discovery_t *d, *next;
+
+  lock_assert(&global_lock);
+
+  next = TAILQ_FIRST(&satip_discoveries);
+  while (next) {
+    d = next;
+    next = TAILQ_NEXT(d, disc_link);
+    if (d->http_client) {
+      if (dispatch_clock - d->http_start > 4)
+        satip_discovery_destroy(d, 1);;
+      continue;
+    }
+    d->http_client = http_connect(&d->url, NULL,
+                                  satip_discovery_http_data,
+                                  satip_discovery_http_fail,
+                                  d);
+    if (d->http_client == NULL)
+      satip_discovery_destroy(d, 1);
+    d->http_start = dispatch_clock;
+  }
+  if (TAILQ_FIRST(&satip_discoveries))
+    gtimer_arm(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 5);
+}
+
+static void
+satip_discovery_service_received
+  (uint8_t *data, size_t len, udp_connection_t *conn,
+   struct sockaddr_storage *storage)
+{
+  char *buf, *ptr, *saveptr;
+  char *argv[10];
+  char *st = NULL;
+  char *location = NULL;
+  char *server = NULL;
+  char *uuid = NULL;
+  char *bootid = NULL;
+  char *configid = NULL;
+  char *deviceid = NULL;
+  char sockbuf[128];
+  satip_discovery_t *d;
+  int n, i;
+
+  if (len > 8191 || satip_discoveries_count > 100)
+    return;
+  buf = alloca(len+1);
+  memcpy(buf, data, len);
+  buf[len] = '\0';
+  ptr = strtok_r(buf, "\r\n", &saveptr);
+  /* Request decoder */
+  if (ptr) {
+    if (http_tokenize(ptr, argv, 3, -1) != 3)
+      return;
+    if (conn->multicast) {
+      if (strcmp(argv[0], "NOTIFY"))
+        return;
+      if (strcmp(argv[1], "*"))
+        return;
+      if (strcmp(argv[2], "HTTP/1.1"))
+        return;
+    } else {
+      if (strcmp(argv[0], "HTTP/1.1"))
+        return;
+      if (strcmp(argv[1], "200"))
+        return;
+    }
+    ptr = strtok_r(NULL, "\r\n", &saveptr);
+  }
+  /* Header decoder */
+  while (1) {
+    if (ptr == NULL)
+      break;
+    if (http_tokenize(ptr, argv, 2, -1) == 2) {
+      if (strcmp(argv[0], "ST:") == 0)
+        st = argv[1];
+      else if (strcmp(argv[0], "LOCATION:") == 0)
+        location = argv[1];
+      else if (strcmp(argv[0], "SERVER:") == 0)
+        server = argv[1];
+      else if (strcmp(argv[0], "BOOTID.UPNP.ORG:") == 0)
+        bootid = argv[1];
+      else if (strcmp(argv[0], "CONFIGID.UPNP.ORG:") == 0)
+        configid = argv[1];
+      else if (strcmp(argv[0], "DEVICEID.SES.COM:") == 0)
+        deviceid = argv[1];
+      else if (strcmp(argv[0], "USN:") == 0) {
+        n = http_tokenize(argv[1], argv, 10, ':');
+        for (i = 0; i < n+1; i++)
+          if (argv[i] && strcmp(argv[i], "uuid") == 0) {
+            uuid = argv[++i];
+            break;
+          }
+      }
+    }
+    ptr = strtok_r(NULL, "\r\n", &saveptr);
+  }
+  /* Sanity checks */
+  if (st == NULL || strcmp(st, "urn:ses-com:device:SatIPServer:1"))
+    return;
+  if (uuid == NULL && strlen(uuid) < 16)
+    return;
+  if (location == NULL && strncmp(location, "http://", 7))
+    return;
+  if (bootid == NULL || configid == NULL || server == NULL)
+    return;
+
+  /* Forward information to next layer */
+
+  d = calloc(1, sizeof(satip_discovery_t));
+  if (inet_ntop(storage->ss_family, IP_IN_ADDR(conn->ip),
+                sockbuf, sizeof(sockbuf)) == NULL) {
+    satip_discovery_destroy(d, 0);
+    return;
+  }
+  d->myaddr   = strdup(sockbuf);
+  d->location = strdup(location);
+  d->server   = strdup(server);
+  d->uuid     = strdup(uuid);
+  d->bootid   = strdup(bootid);
+  d->configid = strdup(configid);
+  d->deviceid = strdup(deviceid ? deviceid : "");
+  if (urlparse(d->location, &d->url)) {
+    satip_discovery_destroy(d, 0);
+    return;
+  }
+
+  pthread_mutex_lock(&global_lock);  
+  i = 1;
+  if (!satip_discovery_find(d) && !satip_device_find(d->uuid)) {
+    TAILQ_INSERT_TAIL(&satip_discoveries, d, disc_link);
+    satip_discoveries_count++;
+    gtimer_arm_ms(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 250);
+    i = 0;
+  }
+  pthread_mutex_unlock(&global_lock);
+  if (i) /* duplicate */
+    satip_discovery_destroy(d, 0);
+}
+
+static void
+satip_discovery_service_destroy(upnp_service_t *us)
+{
+  satip_discovery_service = NULL;
+}
+
+static void
+satip_discovery_timer_cb(void *aux)
+{
+#define MSG "\
+M-SEARCH * HTTP/1.1\r\n\
+HOST: 239.255.255.250:1900\r\n\
+MAN: \"ssdp:discover\"\r\n\
+MX: 2\r\n\
+ST: urn:ses-com:device:SatIPServer:1\r\n\
+\r\n"
+  htsbuf_queue_t q;
+
+  if (!tvheadend_running)
+    return;
+  if (!upnp_running) {
+    gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1);
+    return;
+  }
+  if (satip_discovery_service == NULL) {
+    satip_discovery_service              = upnp_service_create(upnp_service);
+    satip_discovery_service->us_received = satip_discovery_service_received;
+    satip_discovery_service->us_destroy  = satip_discovery_service_destroy;
+  }
+  htsbuf_queue_init(&q, 0);
+  htsbuf_append(&q, MSG, sizeof(MSG)-1);
+  upnp_send(&q, NULL);
+  htsbuf_queue_flush(&q); 
+  gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 3600);
+#undef MSG
+}
+
+/*
+ * Initialization
+ */
+
+void satip_init ( void )
+{
+  TAILQ_INIT(&satip_discoveries);
+  gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1);
+}
+
+void satip_done ( void )
+{
+  tvh_hardware_t *th, *n;
+  satip_discovery_t *d, *nd;
+
+  pthread_mutex_lock(&global_lock);
+  for (th = LIST_FIRST(&tvh_hardware); th != NULL; th = n) {
+    n = LIST_NEXT(th, th_link);
+    if (idnode_is_instance(&th->th_id, &satip_device_class)) {
+      satip_device_destroy((satip_device_t *)th);
+    }
+  }
+  for (d = TAILQ_FIRST(&satip_discoveries); d != NULL; d = nd) {
+    nd = TAILQ_NEXT(d, disc_link);
+    satip_discovery_destroy(d, 1);
+  }
+  pthread_mutex_unlock(&global_lock);
+}
diff --git a/src/input/mpegts/satip/satip.h b/src/input/mpegts/satip/satip.h
new file mode 100644 (file)
index 0000000..6e994c8
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  Tvheadend - SAT-IP DVB private data
+ *
+ *  Copyright (C) 2014 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/>.
+ */
+
+#ifndef __TVH_SATIP_H__
+#define __TVH_SATIP_H__
+
+void satip_init( void );
+void satip_done( void );
+
+#endif /* __TVH_SATIP_H__ */
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
new file mode 100644 (file)
index 0000000..d6568e3
--- /dev/null
@@ -0,0 +1,1120 @@
+/*
+ *  Tvheadend - SAT>IP DVB frontend
+ *
+ *  Copyright (C) 2014 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/>.
+ */
+
+#define _GNU_SOURCE
+#include <sys/socket.h>
+#include <fcntl.h>
+#include "tvheadend.h"
+#include "tvhpoll.h"
+#include "streaming.h"
+#include "http.h"
+#include "satip_private.h"
+
+static int
+satip_frontend_tune1
+  ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi );
+
+/* **************************************************************************
+ * Class definition
+ * *************************************************************************/
+
+static void
+satip_frontend_class_save ( idnode_t *in )
+{
+  satip_device_t *la = ((satip_frontend_t*)in)->sf_device;
+  satip_device_save(la);
+}
+
+const idclass_t satip_frontend_class =
+{
+  .ic_super      = &mpegts_input_class,
+  .ic_class      = "satip_frontend",
+  .ic_caption    = "SAT>IP DVB Frontend",
+  .ic_save       = satip_frontend_class_save,
+  .ic_properties = (const property_t[]) {
+    {
+      .type     = PT_INT,
+      .id       = "fe_number",
+      .name     = "Frontend Number",
+      .opts     = PO_RDONLY | PO_NOSAVE,
+      .off      = offsetof(satip_frontend_t, sf_number),
+    },
+    {
+      .type     = PT_BOOL,
+      .id       = "fullmux",
+      .name     = "Full Mux Rx mode",
+      .off      = offsetof(satip_frontend_t, sf_fullmux),
+    },
+    {
+      .type     = PT_INT,
+      .id       = "udp_rtp_port",
+      .name     = "UDP RTP Port Number (2 ports)",
+      .off      = offsetof(satip_frontend_t, sf_udp_rtp_port),
+    },
+    {}
+  }
+};
+
+const idclass_t satip_frontend_dvbt_class =
+{
+  .ic_super      = &satip_frontend_class,
+  .ic_class      = "satip_frontend_dvbt",
+  .ic_caption    = "SAT>IP DVB-T Frontend",
+  .ic_properties = (const property_t[]){
+    {}
+  }
+};
+
+static idnode_set_t *
+satip_frontend_dvbs_class_get_childs ( idnode_t *self )
+{
+  satip_frontend_t   *lfe = (satip_frontend_t*)self;
+  idnode_set_t        *is  = idnode_set_create();
+  satip_satconf_t *sfc;
+  TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link)
+    idnode_set_add(is, &sfc->sfc_id, NULL);
+  return is;
+}
+
+static int
+satip_frontend_dvbs_class_positions_set ( void *self, const void *val )
+{
+  satip_frontend_t *lfe = self;
+  int n = *(int *)val;
+
+  if (n < 0 || n > 32)
+    return 0;
+  if (n != lfe->sf_positions) {
+    lfe->sf_positions = n;
+    satip_satconf_updated_positions(lfe);
+    return 1;
+  }
+  return 0;
+}
+
+const idclass_t satip_frontend_dvbs_class =
+{
+  .ic_super      = &satip_frontend_class,
+  .ic_class      = "satip_frontend_dvbs",
+  .ic_caption    = "SAT>IP DVB-S Frontend",
+  .ic_get_childs = satip_frontend_dvbs_class_get_childs,
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_INT,
+      .id       = "positions",
+      .name     = "Sattellite Positions",
+      .set      = satip_frontend_dvbs_class_positions_set,
+      .opts     = PO_NOSAVE,
+      .off      = offsetof(satip_frontend_t, sf_positions),
+      .def.i    = 4
+    },
+    {
+      .id       = "networks",
+      .type     = PT_NONE,
+    },
+    {}
+  }
+};
+
+/* **************************************************************************
+ * Class methods
+ * *************************************************************************/
+
+static int
+satip_frontend_is_free ( mpegts_input_t *mi )
+{
+  satip_device_t *sd = ((satip_frontend_t*)mi)->sf_device;
+  satip_frontend_t *lfe;
+  TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link)
+    if (!mpegts_input_is_free((mpegts_input_t*)lfe))
+      return 0;
+  return 1;
+}
+
+static int
+satip_frontend_get_weight ( mpegts_input_t *mi )
+{
+  int weight = 0;
+  satip_device_t *sd = ((satip_frontend_t*)mi)->sf_device;
+  satip_frontend_t *lfe;
+  TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link)
+    weight = MAX(weight, mpegts_input_get_weight((mpegts_input_t*)lfe));
+  return weight;
+}
+
+static int
+satip_frontend_get_priority ( mpegts_input_t *mi, mpegts_mux_t *mm )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  int r = mpegts_input_get_priority(mi, mm);
+  if (lfe->sf_positions)
+    r += satip_satconf_get_priority(lfe, mm);
+  return r;
+}
+
+static int
+satip_frontend_get_grace ( mpegts_input_t *mi, mpegts_mux_t *mm )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  int r = 5;
+  if (lfe->sf_positions)
+    r = 10;
+  return r;
+}
+
+static int
+satip_frontend_is_enabled ( mpegts_input_t *mi )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  if (!lfe->mi_enabled) return 0;
+  return 1;
+}
+
+static void
+satip_frontend_stop_mux
+  ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  char buf1[256], buf2[256];
+
+  mi->mi_display_name(mi, buf1, sizeof(buf1));
+  mmi->mmi_mux->mm_display_name(mmi->mmi_mux, buf2, sizeof(buf2));
+  tvhdebug("satip", "%s - stopping %s", buf1, buf2);
+
+  lfe->sf_running   = 0;
+  lfe->sf_mmi       = NULL;
+
+  gtimer_disarm(&lfe->sf_monitor_timer);
+
+  /* Stop thread */
+  if (lfe->sf_dvr_pipe.wr > 0) {
+    tvh_write(lfe->sf_dvr_pipe.wr, "", 1);
+    tvhtrace("satip", "%s - waiting for dvr thread", buf1);
+    pthread_join(lfe->sf_dvr_thread, NULL);
+    tvh_pipe_close(&lfe->sf_dvr_pipe);
+    tvhdebug("satip", "%s - stopped dvr thread", buf1);
+  }
+
+  udp_close(lfe->sf_rtp);   lfe->sf_rtp        = NULL;
+  udp_close(lfe->sf_rtcp);  lfe->sf_rtcp       = NULL;
+
+  free(lfe->sf_pids);       lfe->sf_pids       = NULL;
+  free(lfe->sf_pids_tuned); lfe->sf_pids_tuned = NULL;
+}
+
+static int
+satip_frontend_start_mux
+  ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  if (lfe->sf_positions > 0)
+    lfe->sf_position = satip_satconf_get_position(lfe, mmi->mmi_mux);
+  return satip_frontend_tune1((satip_frontend_t*)mi, mmi);
+}
+
+static int
+satip_frontend_add_pid( satip_frontend_t *lfe, int pid)
+{
+  int mid, div;
+
+  if (pid < 0 || pid >= 8191)
+    return 0;
+
+  pthread_mutex_lock(&lfe->sf_dvr_lock);
+  if (lfe->sf_pids_count >= lfe->sf_pids_size) {
+    lfe->sf_pids_size += 64;
+    lfe->sf_pids       = realloc(lfe->sf_pids,
+                                 lfe->sf_pids_size * sizeof(uint16_t));
+    lfe->sf_pids_tuned = realloc(lfe->sf_pids_tuned,
+                                 lfe->sf_pids_size * sizeof(uint16_t));
+  }
+
+  if (lfe->sf_pids_count == 0) {
+    lfe->sf_pids[lfe->sf_pids_count++] = pid;
+    pthread_mutex_unlock(&lfe->sf_dvr_lock);
+    return 1;
+  }
+
+#if 0
+  printf("Insert PID: %i\n", pid);
+  if (pid == 0)
+    printf("HERE!!!\n");
+  { int i; for (i = 0; i < lfe->sf_pids_count; i++)
+    printf("Bpid[%i] = %i\n", i, lfe->sf_pids[i]); }
+#endif
+  /* insert pid to the sorted array */
+  mid = div = lfe->sf_pids_count / 2;
+  while (1) {
+    assert(mid >= 0 && mid < lfe->sf_pids_count);
+    if (div > 1)
+      div /= 2;
+    if (lfe->sf_pids[mid] == pid) {
+      pthread_mutex_unlock(&lfe->sf_dvr_lock);
+      return 0;
+    }
+    if (lfe->sf_pids[mid] < pid) {
+      if (mid + 1 >= lfe->sf_pids_count) {
+        lfe->sf_pids[lfe->sf_pids_count++] = pid;
+        break;
+      }
+      if (lfe->sf_pids[mid + 1] > pid) {
+        mid++;
+        if (mid < lfe->sf_pids_count)
+          memmove(&lfe->sf_pids[mid + 1], &lfe->sf_pids[mid],
+                  (lfe->sf_pids_count - mid) * sizeof(uint16_t));
+        lfe->sf_pids[mid] = pid;
+        lfe->sf_pids_count++;
+        break;
+      }
+      mid += div;
+    } else {
+      if (mid == 0 || lfe->sf_pids[mid - 1] < pid) {
+        memmove(&lfe->sf_pids[mid+1], &lfe->sf_pids[mid],
+                (lfe->sf_pids_count - mid) * sizeof(uint16_t));
+        lfe->sf_pids[mid] = pid;
+        lfe->sf_pids_count++;
+        break;
+      }
+      mid -= div;
+    }
+  }
+#if 0
+  { int i; for (i = 0; i < lfe->sf_pids_count; i++)
+    printf("Apid[%i] = %i\n", i, lfe->sf_pids[i]); }
+#endif
+  pthread_mutex_unlock(&lfe->sf_dvr_lock);
+  return 1;
+}
+
+static mpegts_pid_t *
+satip_frontend_open_pid
+  ( mpegts_input_t *mi, mpegts_mux_t *mm, int pid, int type, void *owner )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  mpegts_pid_t *mp;
+  int change = 0;
+
+  if (!(mp = mpegts_input_open_pid(mi, mm, pid, type, owner)))
+    return NULL;
+
+  if (type == MPEGTS_FULLMUX_PID) {
+    if (lfe->sf_device->sd_fullmux_ok) {
+      if (!lfe->sf_pids_any)
+        lfe->sf_pids_any = change = 1;
+    } else {
+      mpegts_service_t *s;
+      elementary_stream_t *st;
+      LIST_FOREACH(s, &mm->mm_services, s_dvb_mux_link) {
+        change |= satip_frontend_add_pid(lfe, s->s_pmt_pid);
+        change |= satip_frontend_add_pid(lfe, s->s_pcr_pid);
+        TAILQ_FOREACH(st, &s->s_components, es_link)
+          change |= satip_frontend_add_pid(lfe, st->es_pid);
+      }
+    }
+  } else {
+    change |= satip_frontend_add_pid(lfe, mp->mp_pid);
+  }
+
+  pthread_mutex_lock(&lfe->sf_dvr_lock);
+  if (change && !lfe->sf_pids_any_tuned)
+    tvh_write(lfe->sf_dvr_pipe.wr, "c", 1);
+  pthread_mutex_unlock(&lfe->sf_dvr_lock);
+
+  return mp;
+}
+
+static void
+satip_frontend_close_pid
+  ( mpegts_input_t *mi, mpegts_mux_t *mm, int pid, int type, void *owner )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  int mid, div;
+
+  /* remove PID */
+  pthread_mutex_lock(&lfe->sf_dvr_lock);
+  if (lfe->sf_pids) {
+    mid = div = lfe->sf_pids_count / 2;
+    while (1) {
+      if (div > 1)
+        div /= 2;
+      if (lfe->sf_pids[mid] == pid) {
+        if (mid + 1 < lfe->sf_pids_count)
+          memmove(&lfe->sf_pids[mid], &lfe->sf_pids[mid+1],
+                  (lfe->sf_pids_count - mid - 1) * sizeof(uint16_t));
+        lfe->sf_pids_count--;
+        break;
+      } else if (lfe->sf_pids[mid] < pid) {
+        if (mid + 1 > lfe->sf_pids_count)
+          break;
+        if (lfe->sf_pids[mid + 1] > pid)
+          break;
+        mid += div;
+      } else {
+        if (mid == 0)
+          break;
+        if (lfe->sf_pids[mid - 1] < pid)
+          break;
+        mid -= div;
+      }
+    }
+  }
+  pthread_mutex_unlock(&lfe->sf_dvr_lock);
+
+  mpegts_input_close_pid(mi, mm, pid, type, owner);
+}
+
+static idnode_set_t *
+satip_frontend_network_list ( mpegts_input_t *mi )
+{
+  satip_frontend_t *lfe = (satip_frontend_t*)mi;
+  const idclass_t     *idc;
+
+  if (lfe->sf_type == DVB_TYPE_T)
+    idc = &dvb_network_dvbt_class;
+  else if (lfe->sf_type == DVB_TYPE_S)
+    idc = &dvb_network_dvbs_class;
+  else
+    return NULL;
+
+  return idnode_find_all(idc);
+}
+
+/* **************************************************************************
+ * Data processing
+ * *************************************************************************/
+
+static void
+satip_frontend_decode_rtcp( satip_frontend_t *lfe, const char *name,
+                            mpegts_mux_instance_t *mmi,
+                            uint8_t *rtcp, size_t len )
+{
+  signal_state_t status;
+  uint16_t l, sl;
+  char *s;
+  char *argv[4];
+  int n;
+
+  /*
+   * DVB-S/S2:
+   * ver=<major>.<minor>;src=<srcID>;tuner=<feID>,<level>,<lock>,<quality>,\
+   * <frequency>,<polarisation>,<system>,<type>,<pilots>,<roll_off>,
+   * <symbol_rate>,<fec_inner>;pids=<pid0>,...,<pidn>
+   *
+   * DVB-T:
+   * ver=1.1;tuner=<feID>,<level>,<lock>,<quality>,<freq>,<bw>,<msys>,<tmode>,\
+   * <mtype>,<gi>,<fec>,<plp>,<t2id>,<sm>;pids=<pid0>,...,<pidn>
+   */
+
+  /* level:
+   * Numerical value between 0 and 255
+   * An incoming L-band satellite signal of
+   * -25dBm corresponds to 224
+   * -65dBm corresponds to 32
+   *  No signal corresponds to 0
+   *
+   * lock:
+   * lock Set to one of the following values:
+   * "0" the frontend is not locked   
+   * "1" the frontend is locked
+   *
+   * quality:
+   * Numerical value between 0 and 15
+   * Lowest value corresponds to highest error rate
+   * The value 15 shall correspond to
+   * -a BER lower than 2x10-4 after Viterbi for DVB-S
+   * -a PER lower than 10-7 for DVB-S2
+   */
+  while (len >= 12) {
+    if ((rtcp[0] & 0xc0) != 0x80)              /* protocol version: v2 */
+      return;
+    l = (((rtcp[2] << 8) | rtcp[3]) + 1) * 4;   /* length of payload */
+    if (l > len)
+      return;
+    if (rtcp[1]  ==  204 && l > 20 &&           /* packet type */
+        rtcp[8]  == 'S'  && rtcp[9]  == 'E' &&
+        rtcp[10] == 'S'  && rtcp[11] == '1') {
+      sl = (rtcp[14] << 8) | rtcp[15];
+      if (sl > 0 && l - 16 >= sl) {
+        rtcp[sl + 16] = '\0';
+        s = (char *)rtcp + 16;
+        tvhtrace("satip", "Status string: '%s'", s);
+        status = SIGNAL_NONE;
+        if (strncmp(s, "ver=1.0;", 8) == 0) {
+          if ((s = strstr(s + 8, ";tuner=")) == NULL)
+            return;
+          s += 7;
+          n = http_tokenize(s, argv, 4, ',');
+          if (n < 4)
+            return;
+          if (atoi(argv[0]) != lfe->sf_number)
+            return;
+          mmi->mmi_stats.signal =
+            (atoi(argv[1]) * 100) / lfe->sf_device->sd_sig_scale;
+          if (atoi(argv[2]) > 0)
+            status = SIGNAL_GOOD;
+          mmi->mmi_stats.snr = atoi(argv[3]);
+          goto ok;          
+        } else if (strncmp(s, "ver=1.1;tuner=", 14) == 0) {
+          n = http_tokenize(s + 14, argv, 4, ',');
+          if (n < 4)
+            return;
+          if (atoi(argv[0]) != lfe->sf_number)
+            return;
+          mmi->mmi_stats.signal =
+            (atoi(argv[1]) * 100) / lfe->sf_device->sd_sig_scale;
+          if (atoi(argv[2]) > 0)
+            status = SIGNAL_GOOD;
+          mmi->mmi_stats.snr = atoi(argv[3]);
+          goto ok;
+        }
+      }
+    }
+    rtcp += l;
+    len -= l;
+  }
+  return;
+
+ok:
+  if (mmi->mmi_stats.snr < 2 && status == SIGNAL_GOOD)
+    status              = SIGNAL_BAD;
+  else if (mmi->mmi_stats.snr < 4 && status == SIGNAL_GOOD)
+    status              = SIGNAL_FAINT;
+  lfe->sf_status        = status;
+}
+
+static void
+satip_frontend_default_tables 
+  ( satip_frontend_t *lfe, mpegts_mux_t *mm )
+{
+  psi_tables_default(mm);
+  psi_tables_dvb(mm);
+}
+
+static void
+satip_frontend_store_pids(char *buf, uint16_t *pids, int count)
+{
+  int first = 1;
+  char *s = buf;
+
+  *s = '\0';
+  while (count--) {
+    assert(*pids < 8192);
+    if (!first)
+      sprintf(s + strlen(s), ",%i", *(pids++));
+    else {
+      sprintf(s + strlen(s), "%i", *(pids++));
+      first = 0;
+    }
+  }
+}
+
+static void
+satip_frontend_pid_changed( satip_rtsp_connection_t *rtsp,
+                            satip_frontend_t *lfe, const char *name )
+{
+  char *add, *del;
+  int i, j, r, count, any = lfe->sf_pids_any;
+  int deleted;
+
+  if (!lfe->sf_running)
+    return;
+
+  pthread_mutex_lock(&lfe->sf_dvr_lock);
+
+  if (lfe->sf_pids_count > lfe->sf_device->sd_pids_max)
+    any = lfe->sf_device->sd_fullmux_ok ? 1 : 0;
+
+  if (any) {
+
+    if (lfe->sf_pids_any_tuned) {
+      pthread_mutex_unlock(&lfe->sf_dvr_lock);
+      return;
+    }
+    lfe->sf_pids_any_tuned = 1;
+    memcpy(lfe->sf_pids_tuned, lfe->sf_pids,
+           lfe->sf_pids_count * sizeof(uint16_t));
+    lfe->sf_pids_tcount = lfe->sf_pids_count;
+    pthread_mutex_unlock(&lfe->sf_dvr_lock);
+
+    r = satip_rtsp_play(rtsp,  "all", NULL, NULL);
+
+  } else if (!lfe->sf_device->sd_pids_deladd ||
+             lfe->sf_pids_any_tuned ||
+             lfe->sf_pids_tcount == 0) {
+
+    lfe->sf_pids_any_tuned = 0;
+    count = lfe->sf_pids_count;
+    if (count > lfe->sf_device->sd_pids_max)
+      count = lfe->sf_device->sd_pids_max;
+    add = alloca(count * 5);
+    /* prioritize higher PIDs (tables are low prio) */
+    satip_frontend_store_pids(add,
+                              &lfe->sf_pids[lfe->sf_pids_count - count],
+                              count);
+    memcpy(lfe->sf_pids_tuned, lfe->sf_pids,
+           lfe->sf_pids_count * sizeof(uint16_t));
+    lfe->sf_pids_tcount = lfe->sf_pids_count;
+    pthread_mutex_unlock(&lfe->sf_dvr_lock);
+
+    r = satip_rtsp_play(rtsp, add, NULL, NULL);
+
+  } else {
+
+    add = alloca(lfe->sf_pids_count * 5);
+    del = alloca(lfe->sf_pids_count * 5);
+    add[0] = del[0] = '\0';
+
+#if 0
+    for (i = 0; i < lfe->sf_pids_count; i++)
+      printf("pid[%i] = %i\n", i, lfe->sf_pids[i]);
+    for (i = 0; i < lfe->sf_pids_tcount; i++)
+      printf("tuned[%i] = %i\n", i, lfe->sf_pids_tuned[i]);
+#endif
+
+    i = j = deleted = 0;
+    while (i < lfe->sf_pids_count && j < lfe->sf_pids_tcount) {
+      if (lfe->sf_pids[i] == lfe->sf_pids_tuned[j]) {
+        i++; j++;
+      } else if (lfe->sf_pids[i] < lfe->sf_pids_tuned[j]) {
+        i++;
+      } else {
+        sprintf(del + strlen(del), ",%i", lfe->sf_pids_tuned[j++]);
+        deleted++;
+      }
+    }
+    while (j < lfe->sf_pids_tcount) {
+      sprintf(del + strlen(del), ",%i", lfe->sf_pids_tuned[j++]);
+      deleted++;
+    }
+
+    count = lfe->sf_pids_count + (lfe->sf_pids_tcount - deleted);
+    if (count > lfe->sf_device->sd_pids_max)
+      count = lfe->sf_device->sd_pids_max;
+    /* prioritize higher PIDs (tables are low prio) */
+    /* count means "skip count" in following code */
+    count = lfe->sf_pids_count - count;
+    
+    i = j = 0;
+    while (i < lfe->sf_pids_count && j < lfe->sf_pids_tcount) {
+      if (lfe->sf_pids[i] == lfe->sf_pids_tuned[j]) {
+        i++; j++;
+      } else if (lfe->sf_pids[i] < lfe->sf_pids_tuned[j]) {
+        if (count > 0) {
+          count--;
+        } else {
+          sprintf(add + strlen(add), ",%i", lfe->sf_pids[i]);
+        }
+        i++;
+      } else {
+        j++;
+      }
+    }
+    while (i < lfe->sf_pids_count) {
+      if (count > 0)
+        count--;
+      else
+        sprintf(add + strlen(add), ",%i", lfe->sf_pids[i++]);
+    }
+
+    memcpy(lfe->sf_pids_tuned, lfe->sf_pids,
+           lfe->sf_pids_count * sizeof(uint16_t));
+    lfe->sf_pids_tcount = lfe->sf_pids_count;
+    pthread_mutex_unlock(&lfe->sf_dvr_lock);
+
+    r = satip_rtsp_play(rtsp, NULL, add, del);
+  }
+
+  if (r < 0)
+    tvherror("satip", "%s - failed to modify pids: %s", name, strerror(-r));
+}
+
+static void *
+satip_frontend_input_thread ( void *aux )
+{
+#define PKTS 64
+  satip_frontend_t *lfe = aux;
+  mpegts_mux_instance_t *mmi = lfe->sf_mmi;
+  satip_rtsp_connection_t *rtsp;
+  dvb_mux_t *lm;
+  char buf[256];
+  uint8_t tsb[PKTS][1356 + 128];
+  uint8_t rtcp[2048];
+  uint8_t *p;
+  sbuf_t sb;
+  struct iovec   iov[PKTS];
+  struct mmsghdr msg[PKTS];
+  int pos, nfds, i, r;
+  size_t c;
+  int tc;
+  tvhpoll_event_t ev[4];
+  tvhpoll_t *efd;
+  int changing = 0, ms = -1, fatal = 0;
+  uint32_t seq = -1, nseq;
+
+  lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf));
+
+  if (lfe->sf_rtp == NULL || lfe->sf_rtcp == NULL || mmi == NULL)
+    return NULL;
+
+  lm = (dvb_mux_t *)mmi->mmi_mux;
+
+  rtsp = satip_rtsp_connection(lfe->sf_device);
+  if (rtsp == NULL)
+    return NULL;
+
+  /* Setup poll */
+  efd = tvhpoll_create(4);
+  memset(ev, 0, sizeof(ev));
+  ev[0].events             = TVHPOLL_IN;
+  ev[0].fd                 = lfe->sf_rtp->fd;
+  ev[0].data.u64           = (uint64_t)lfe->sf_rtp;
+  ev[1].events             = TVHPOLL_IN;
+  ev[1].fd                 = lfe->sf_rtcp->fd;
+  ev[1].data.u64           = (uint64_t)lfe->sf_rtcp;
+  ev[2].events             = TVHPOLL_IN;
+  ev[2].fd                 = rtsp->fd;
+  ev[2].data.u64           = (uint64_t)rtsp;
+  ev[3].events             = TVHPOLL_IN;
+  ev[3].fd                 = lfe->sf_dvr_pipe.rd;
+  ev[3].data.u64           = 0;
+  tvhpoll_add(efd, ev, 4);
+
+  /* Read */
+  memset(&msg, 0, sizeof(msg));
+  for (i = 0; i < PKTS; i++) {
+    msg[i].msg_hdr.msg_iov    = &iov[i];
+    msg[i].msg_hdr.msg_iovlen = 1;
+    iov[i].iov_base           = tsb[i];
+    iov[i].iov_len            = sizeof(tsb[0]);
+  }
+
+  r = satip_rtsp_setup(rtsp,
+                       lfe->sf_position, lfe->sf_number,
+                       lfe->sf_rtp_port, &lm->lm_tuning,
+                       1);
+  if (r < 0) {
+    tvherror("satip", "%s - failed to tune", buf);
+    return NULL;
+  }
+
+  sbuf_init_fixed(&sb, 18800);
+
+  while (tvheadend_running && !fatal) {
+
+    nfds = tvhpoll_wait(efd, ev, 1, ms);
+
+    if (nfds > 0 && ev[0].data.u64 == 0) {
+      c = read(lfe->sf_dvr_pipe.rd, tsb[0], 1);
+      if (c == 1 && tsb[0][0] == 'c') {
+        ms = 20;
+        changing = 1;
+        continue;
+      }
+      tvhtrace("satip", "%s - input thread received shutdown", buf);
+      break;
+    }
+
+    if (changing && rtsp->cmd == SATIP_RTSP_CMD_NONE) {
+      ms = -1;
+      changing = 0;
+      satip_frontend_pid_changed(rtsp, lfe, buf);
+      continue;
+    }
+
+    if (nfds < 1) continue;
+
+    if (ev[0].data.u64 == (uint64_t)rtsp) {
+      r = satip_rtsp_receive(rtsp);
+      if (r < 0) {
+        tvhlog(LOG_ERR, "satip", "%s - RTSP error %d (%s) [%i-%i]",
+               buf, r, strerror(-r), rtsp->cmd, rtsp->code);
+        fatal = 1;
+      } else if (r) {
+        switch (rtsp->cmd) {
+        case SATIP_RTSP_CMD_OPTIONS:
+          r = satip_rtsp_options_decode(rtsp);
+          if (r < 0) {
+            tvhlog(LOG_ERR, "satip", "%s - RTSP OPTIONS error %d (%s) [%i-%i]",
+                   buf, r, strerror(-r), rtsp->cmd, rtsp->code);
+            fatal = 1;
+          }
+          break;
+        case SATIP_RTSP_CMD_SETUP:
+          r = satip_rtsp_setup_decode(rtsp);
+          if (r < 0 || rtsp->client_port != lfe->sf_rtp_port) {
+            tvhlog(LOG_ERR, "satip", "%s - RTSP SETUP error %d (%s) [%i-%i]",
+                   buf, r, strerror(-r), rtsp->cmd, rtsp->code);
+            fatal = 1;
+          } else {
+            tvhdebug("satip", "%s #%i - new session %s stream id %li",
+                        lfe->sf_device->sd_info.addr, lfe->sf_number,
+                        rtsp->session, rtsp->stream_id);
+            pthread_mutex_lock(&global_lock);
+            satip_frontend_default_tables(lfe, mmi->mmi_mux);
+            pthread_mutex_unlock(&global_lock);
+            satip_frontend_pid_changed(rtsp, lfe, buf);
+          }
+          break;
+        default:
+          if (rtsp->code >= 400) {
+            tvhlog(LOG_ERR, "satip", "%s - RTSP cmd error %d (%s) [%i-%i]",
+                   buf, r, strerror(-r), rtsp->cmd, rtsp->code);
+            fatal = 1;
+          }
+          break;
+        }
+        rtsp->cmd = SATIP_RTSP_CMD_NONE;
+      }
+    }
+
+    if (rtsp->ping_time + rtsp->timeout / 2 < dispatch_clock &&
+        rtsp->cmd == SATIP_RTSP_CMD_NONE)
+      satip_rtsp_options(rtsp);
+
+    if (ev[0].data.u64 == (uint64_t)lfe->sf_rtcp) {
+      c = recv(lfe->sf_rtcp->fd, rtcp, sizeof(rtcp), MSG_DONTWAIT);
+      if (c > 0)
+        satip_frontend_decode_rtcp(lfe, buf, mmi, rtcp, c);
+      continue;
+    }
+    
+    if (ev[0].data.u64 != (uint64_t)lfe->sf_rtp)
+      continue;     
+
+    tc = recvmmsg(lfe->sf_rtp->fd, msg, PKTS, MSG_DONTWAIT, NULL);
+
+    if (tc < 0) {
+      if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
+        continue;
+      if (errno == EOVERFLOW) {
+        tvhlog(LOG_WARNING, "satip", "%s - recvmsg() EOVERFLOW", buf);
+        continue;
+      }
+      tvhlog(LOG_ERR, "satip", "%s - recv() error %d (%s)",
+             buf, errno, strerror(errno));
+      break;
+    }
+
+    for (i = 0; i < tc; i++) {
+      p = tsb[i];
+      c = msg[i].msg_len;
+
+      /* Strip RTP header */
+      if (c < 12)
+        continue;
+      if ((p[0] & 0xc0) != 0x80)
+        continue;
+      if ((p[1] & 0x7f) != 33)
+        continue;
+      pos = ((p[0] & 0x0f) * 4) + 12;
+      if (p[0] & 0x10) {
+        if (c < pos + 4)
+          continue;
+        pos += (((p[pos+2] << 8) | p[pos+3]) + 1) * 4;
+      }
+      if (c <= pos || ((c - pos) % 188) != 0)
+        continue;
+      /* Use uncorrectable value to notify RTP delivery issues */
+      nseq = (p[2] << 8) | p[3];
+      if (seq == -1)
+        seq = nseq;
+      else if (((seq + 1) & 0xffff) != nseq)
+        mmi->mmi_stats.unc++;
+      seq = nseq;
+      /* Process */
+      sbuf_append(&sb, p + pos, c - pos);
+      mpegts_input_recv_packets((mpegts_input_t*)lfe, mmi,
+                                &sb, 0, NULL, NULL);
+    }
+  }
+
+  sbuf_free(&sb);
+
+  ev[0].events             = TVHPOLL_IN;
+  ev[0].fd                 = lfe->sf_rtp->fd;
+  ev[0].data.u64           = (uint64_t)lfe->sf_rtp;
+  ev[1].events             = TVHPOLL_IN;
+  ev[1].fd                 = lfe->sf_rtcp->fd;
+  ev[1].data.u64           = (uint64_t)lfe->sf_rtcp;
+  ev[2].events             = TVHPOLL_IN;
+  ev[2].fd                 = lfe->sf_dvr_pipe.rd;
+  ev[2].data.u64           = 0;
+  tvhpoll_rem(efd, ev, 3);
+
+  if (rtsp->stream_id) {
+    r = satip_rtsp_teardown(rtsp);
+    if (r < 0) {
+      tvhtrace("satip", "%s - bad teardown", buf);
+    } else {
+      while (1) {
+        tvhpoll_wait(efd, ev, 1, -1);
+        r = satip_rtsp_receive(rtsp);
+        if (r)
+          break;
+      }
+    }
+  }
+  satip_rtsp_connection_close(rtsp);
+
+  tvhpoll_destroy(efd);
+  return NULL;
+#undef PKTS
+}
+
+/* **************************************************************************
+ * Tuning
+ * *************************************************************************/
+
+static void
+satip_frontend_signal_cb( void *aux )
+{
+  satip_frontend_t      *lfe = aux;
+  mpegts_mux_instance_t *mmi = LIST_FIRST(&lfe->mi_mux_active);
+  streaming_message_t    sm;
+  signal_status_t        sigstat;
+  service_t             *svc;
+
+  if (mmi == NULL)
+    return;
+  sigstat.status_text  = signal2str(lfe->sf_status);
+  sigstat.snr          = mmi->mmi_stats.snr;
+  sigstat.signal       = mmi->mmi_stats.signal;
+  sigstat.ber          = mmi->mmi_stats.ber;
+  sigstat.unc          = mmi->mmi_stats.unc;
+  sm.sm_type = SMT_SIGNAL_STATUS;
+  sm.sm_data = &sigstat;
+  LIST_FOREACH(svc, &lfe->mi_transports, s_active_link) {
+    pthread_mutex_lock(&svc->s_stream_mutex);
+    streaming_pad_deliver(&svc->s_streaming_pad, &sm);
+    pthread_mutex_unlock(&svc->s_stream_mutex);
+  }
+  gtimer_arm_ms(&lfe->sf_monitor_timer, satip_frontend_signal_cb, lfe, 250);
+}
+
+static int
+satip_frontend_tune0
+  ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi )
+{
+  mpegts_mux_instance_t *cur = LIST_FIRST(&lfe->mi_mux_active);
+  udp_connection_t *uc1 = NULL, *uc2 = NULL;
+  int res = 0;
+
+  if (cur != NULL) {
+    /* Already tuned */
+    if (mmi == cur)
+      return 0;
+
+    /* Stop current */
+    cur->mmi_mux->mm_stop(cur->mmi_mux, 1);
+  }
+  assert(LIST_FIRST(&lfe->mi_mux_active) == NULL);
+
+  assert(lfe->sf_pids == NULL);
+  assert(lfe->sf_pids_tuned == NULL);
+  lfe->sf_pids_count      = 0;
+  lfe->sf_pids_tcount     = 0;
+  lfe->sf_pids_size       = 512;
+  lfe->sf_pids            = calloc(lfe->sf_pids_size, sizeof(uint16_t));
+  lfe->sf_pids_tuned      = calloc(lfe->sf_pids_size, sizeof(uint16_t));
+  lfe->sf_pids_any        = 0;
+  lfe->sf_pids_any_tuned  = 0;
+  lfe->sf_status          = SIGNAL_NONE;
+
+retry:
+  if (lfe->sf_rtp == NULL) {
+    lfe->sf_rtp = udp_bind("satip", "satip_rtp",
+                           lfe->sf_device->sd_info.myaddr,
+                           lfe->sf_udp_rtp_port,
+                           NULL, SATIP_BUF_SIZE);
+    if (lfe->sf_rtp == NULL || lfe->sf_rtp == UDP_FATAL_ERROR)
+      res = SM_CODE_TUNING_FAILED;
+    else
+      lfe->sf_rtp_port = ntohs(IP_PORT(lfe->sf_rtp->ip));
+  }
+  if (lfe->sf_rtcp == NULL && !res) {
+    lfe->sf_rtcp = udp_bind("satip", "satip_rtcp",
+                            lfe->sf_device->sd_info.myaddr,
+                            lfe->sf_rtp_port + 1,
+                            NULL, 16384);
+    if (lfe->sf_rtcp == NULL || lfe->sf_rtcp == UDP_FATAL_ERROR) {
+      if (lfe->sf_udp_rtp_port > 0)
+        res = SM_CODE_TUNING_FAILED;
+      else if (uc1 && uc2)
+        res = SM_CODE_TUNING_FAILED;
+      /* try to find another free UDP port */
+      if (!res) {
+        if (uc1 == NULL)
+          uc1 = lfe->sf_rtp;
+        else
+          uc2 = lfe->sf_rtp;
+        lfe->sf_rtp = NULL;
+        goto retry;
+      }
+    }
+  }
+  udp_close(uc1);
+  udp_close(uc2);
+
+  if (!res) {
+    lfe->sf_mmi = mmi;
+
+    tvh_pipe(O_NONBLOCK, &lfe->sf_dvr_pipe);
+    tvhthread_create(&lfe->sf_dvr_thread, NULL,
+                     satip_frontend_input_thread, lfe, 0);
+
+    gtimer_arm_ms(&lfe->sf_monitor_timer, satip_frontend_signal_cb, lfe, 250);
+
+    lfe->sf_running = 1;
+  }
+
+  return res;
+}
+
+static int
+satip_frontend_tune1
+  ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi )
+{
+  char buf1[256], buf2[256];
+
+  lfe->mi_display_name((mpegts_input_t*)lfe, buf1, sizeof(buf1));
+  mmi->mmi_mux->mm_display_name(mmi->mmi_mux, buf2, sizeof(buf2));
+  tvhdebug("satip", "%s - starting %s", buf1, buf2);
+
+  /* Tune */
+  tvhtrace("satip", "%s - tuning", buf1);
+  return satip_frontend_tune0(lfe, mmi);
+}
+
+/* **************************************************************************
+ * Creation/Config
+ * *************************************************************************/
+
+satip_frontend_t *
+satip_frontend_create
+  ( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num )
+{
+  const idclass_t *idc;
+  const char *uuid = NULL;
+  char id[12], lname[256];
+  satip_frontend_t *lfe;
+
+  /* Internal config ID */
+  snprintf(id, sizeof(id), "%s #%d", dvb_type2str(type), num);
+  if (conf)
+    conf = htsmsg_get_map(conf, id);
+  if (conf)
+    uuid = htsmsg_get_str(conf, "uuid");
+
+  /* Class */
+  if (type == DVB_TYPE_S)
+    idc = &satip_frontend_dvbs_class;
+  else if (type == DVB_TYPE_T)
+    idc = &satip_frontend_dvbt_class;
+  else {
+    tvherror("satip", "unknown FE type %d", type);
+    return NULL;
+  }
+
+  // Note: there is a bit of a chicken/egg issue below, without the
+  //       correct "fe_type" we cannot set the network (which is done
+  //       in mpegts_input_create()). So we must set early.
+  lfe = calloc(1, sizeof(satip_frontend_t));
+  lfe->sf_number   = num;
+  lfe->sf_type     = type;
+  lfe->sf_type_t2  = t2;
+  TAILQ_INIT(&lfe->sf_satconf);
+  pthread_mutex_init(&lfe->sf_dvr_lock, NULL);
+  lfe = (satip_frontend_t*)mpegts_input_create0((mpegts_input_t*)lfe, idc, uuid, conf);
+  if (!lfe) return NULL;
+
+  /* Defaults */
+  lfe->sf_position     = -1;
+
+  /* Callbacks */
+  lfe->mi_is_free      = satip_frontend_is_free;
+  lfe->mi_get_weight   = satip_frontend_get_weight;
+  lfe->mi_get_priority = satip_frontend_get_priority;
+  lfe->mi_get_grace    = satip_frontend_get_grace;
+
+  /* Default name */
+  if (!lfe->mi_name) {
+    snprintf(lname, sizeof(lname), "SAT>IP %s Tuner %s #%i",
+             dvb_type2str(type), sd->sd_info.addr, num);
+    lfe->mi_name = strdup(lname);
+  }
+
+  /* Input callbacks */
+  lfe->mi_is_enabled     = satip_frontend_is_enabled;
+  lfe->mi_start_mux      = satip_frontend_start_mux;
+  lfe->mi_stop_mux       = satip_frontend_stop_mux;
+  lfe->mi_network_list   = satip_frontend_network_list;
+  lfe->mi_open_pid       = satip_frontend_open_pid;
+  lfe->mi_close_pid      = satip_frontend_close_pid;
+
+  /* Adapter link */
+  lfe->sf_device = sd;
+  TAILQ_INSERT_TAIL(&sd->sd_frontends, lfe, sf_link);
+
+  /* Create satconf */
+  if (lfe->sf_type == DVB_TYPE_S)
+    satip_satconf_create(lfe, conf);
+
+  return lfe;
+}
+
+void
+satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe )
+{
+  char id[12];
+  htsmsg_t *m = htsmsg_create_map();
+
+  /* Save frontend */
+  mpegts_input_save((mpegts_input_t*)lfe, m);
+  htsmsg_add_str(m, "type", dvb_type2str(lfe->sf_type));
+  satip_satconf_save(lfe, m);
+  if (lfe->ti_id.in_class == &satip_frontend_dvbs_class)
+    htsmsg_delete_field(m, "networks");
+
+  /* Add to list */
+  snprintf(id, sizeof(id), "%s #%d", dvb_type2str(lfe->sf_type), lfe->sf_number);
+  htsmsg_add_msg(fe, id, m);
+}
+
+void
+satip_frontend_delete ( satip_frontend_t *lfe )
+{
+  mpegts_mux_instance_t *mmi;
+
+  lock_assert(&global_lock);
+
+  /* Ensure we're stopped */
+  if ((mmi = LIST_FIRST(&lfe->mi_mux_active)))
+    mmi->mmi_mux->mm_stop(mmi->mmi_mux, 1);
+
+  gtimer_disarm(&lfe->sf_monitor_timer);
+
+  /* Remove from adapter */
+  TAILQ_REMOVE(&lfe->sf_device->sd_frontends, lfe, sf_link);
+
+  /* Delete satconf */
+  satip_satconf_destroy(lfe);
+
+  /* Finish */
+  mpegts_input_delete((mpegts_input_t*)lfe, 0);
+}
diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h
new file mode 100644 (file)
index 0000000..a064b2f
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ *  Tvheadend - SAT-IP DVB private data
+ *
+ *  Copyright (C) 2014 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/>.
+ */
+
+#ifndef __TVH_SATIP_PRIVATE_H__
+#define __TVH_SATIP_PRIVATE_H__
+
+#include "input.h"
+#include "htsbuf.h"
+#include "udp.h"
+#include "satip.h"
+
+#define SATIP_BUF_SIZE    (4000*188)
+
+typedef struct satip_device_info satip_device_info_t;
+typedef struct satip_device      satip_device_t;
+typedef struct satip_frontend    satip_frontend_t;
+typedef struct satip_satconf     satip_satconf_t;
+
+struct satip_device_info
+{
+  char *myaddr;       /* IP address of this host received data from the SAT>IP device */
+  char *addr;         /* IP address */
+  char *uuid;
+  char *bootid;
+  char *configid;
+  char *deviceid;
+  char *location;     /*< URL of the XML file */
+  char *server;
+  char *friendlyname;
+  char *manufacturer;
+  char *manufacturerURL;
+  char *modeldesc;
+  char *modelname;
+  char *modelnum;
+  char *serialnum;
+  char *presentation;
+  char *tunercfg;     /*< XML urn:ses-com:satipX_SATIPCAP contents */
+};
+
+struct satip_device
+{
+  tvh_hardware_t;
+
+  /*
+   * Adapter info
+   */
+  satip_device_info_t        sd_info;
+
+  /*
+   * Frontends
+   */
+  TAILQ_HEAD(,satip_frontend) sd_frontends;
+
+  /*
+   * RTSP
+   */
+  int                        sd_fullmux_ok;
+  int                        sd_pids_max;
+  int                        sd_pids_len;
+  int                        sd_pids_deladd;
+  int                        sd_sig_scale;
+
+  int                        sd_rtsp_running;
+  pthread_t                  sd_rtsp_tid;
+  pthread_mutex_t            sd_rtsp_lock;
+  pthread_cond_t             sd_rtsp_cond;
+  TAILQ_HEAD(,satip_rtsp_request) sd_rtsp_queue;
+  time_t                     sd_rtsp_ping;
+  gtimer_t                   sd_rtsp_shutdown;
+};
+
+struct satip_frontend
+{
+  mpegts_input_t;
+
+  /*
+   * Device
+   */
+  satip_device_t            *sf_device;
+  TAILQ_ENTRY(satip_frontend) sf_link;
+
+  /*
+   * Frontend info
+   */
+  int                        sf_number;
+  dvb_fe_type_t              sf_type;
+  int                        sf_type_t2;
+  int                        sf_udp_rtp_port;
+  int                        sf_fullmux;
+
+  /*
+   * Reception
+   */
+  pthread_t                  sf_dvr_thread;
+  th_pipe_t                  sf_dvr_pipe;
+  pthread_mutex_t            sf_dvr_lock;
+  pthread_cond_t             sf_dvr_cond;
+  uint16_t                  *sf_pids;
+  uint16_t                  *sf_pids_tuned;
+  int                        sf_pids_any;
+  int                        sf_pids_any_tuned;
+  int                        sf_pids_size;
+  int                        sf_pids_count;
+  int                        sf_pids_tcount;     /*< tuned count */
+  int                        sf_running;
+  int                        sf_position;
+  udp_connection_t          *sf_rtp;
+  udp_connection_t          *sf_rtcp;
+  int                        sf_rtp_port;
+  mpegts_mux_instance_t     *sf_mmi;
+  signal_state_t             sf_status;
+  gtimer_t                   sf_monitor_timer;
+  /*
+   * Configuration
+   */
+  int                        sf_positions;
+  TAILQ_HEAD(,satip_satconf) sf_satconf;
+};
+
+struct satip_satconf
+{
+
+  idnode_t                   sfc_id;
+  /*
+   * Parent
+   */
+  satip_frontend_t          *sfc_lfe;
+  TAILQ_ENTRY(satip_satconf) sfc_link; 
+
+  /*
+   * Config
+   */
+  int                        sfc_enabled;
+  int                        sfc_position;
+  int                        sfc_priority;
+  char                      *sfc_name;
+
+  /*
+   * Assigned networks to this SAT configuration
+   */
+  idnode_set_t              *sfc_networks;
+};
+
+/*
+ * Methods
+ */
+  
+void satip_device_init ( void );
+
+void satip_device_done ( void );
+
+void satip_device_save ( satip_device_t *sd );
+
+void satip_device_destroy ( satip_device_t *sd );
+
+satip_frontend_t *
+satip_frontend_create
+  ( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num );
+
+void satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *m );
+
+void satip_frontend_delete ( satip_frontend_t *lfe );
+
+/*
+ * SAT>IP Satconf configuration
+ */
+void satip_satconf_save ( satip_frontend_t *lfe, htsmsg_t *m );
+
+void satip_satconf_destroy ( satip_frontend_t *lfe );
+
+void satip_satconf_create
+  ( satip_frontend_t *lfe, htsmsg_t *conf );
+
+void satip_satconf_updated_positions
+  ( satip_frontend_t *lfe );
+
+int satip_satconf_get_priority
+  ( satip_frontend_t *lfe, mpegts_mux_t *mm );
+
+int satip_satconf_get_position
+  ( satip_frontend_t *lfe, mpegts_mux_t *mm );
+
+/*
+ * RTSP part
+ */
+
+typedef enum {
+  SATIP_RTSP_CMD_NONE,
+  SATIP_RTSP_CMD_OPTIONS,
+  SATIP_RTSP_CMD_SETUP,
+  SATIP_RTSP_CMD_PLAY,
+  SATIP_RTSP_CMD_TEARDOWN,
+  SATIP_RTSP_CMD_DESCRIBE
+} satip_rtsp_cmd_t;
+
+typedef struct satip_rtsp_connection {
+  /* decoded answer */
+  int              cseq;
+  int              code;
+  char            *header;
+  char            *data;
+  /* state variables */
+  int              sending;
+  satip_rtsp_cmd_t cmd;
+  int              port;
+  int              client_port;
+  int              timeout;
+  char            *session;
+  uint64_t         stream_id;
+  /* internal data */
+  satip_device_t  *device;
+  int              fd;
+  char             rbuf[4096];
+  size_t           rsize;
+  char            *wbuf;
+  size_t           wpos;
+  size_t           wsize;
+  htsbuf_queue_t   wq2;
+  satip_rtsp_cmd_t wq2_cmd;
+  int              wq2_loaded;
+  time_t           ping_time;
+} satip_rtsp_connection_t;
+
+satip_rtsp_connection_t *
+satip_rtsp_connection( satip_device_t *sd );
+
+void
+satip_rtsp_connection_close( satip_rtsp_connection_t *conn );
+
+int
+satip_rtsp_send_partial( satip_rtsp_connection_t *conn );
+
+int 
+satip_rtsp_send( satip_rtsp_connection_t *conn, htsbuf_queue_t *q,
+                 satip_rtsp_cmd_t cmd );
+
+int
+satip_rtsp_receive( satip_rtsp_connection_t *conn );
+
+int
+satip_rtsp_options_decode( satip_rtsp_connection_t *conn );
+
+void
+satip_rtsp_options( satip_rtsp_connection_t *conn );
+
+int
+satip_rtsp_setup_decode( satip_rtsp_connection_t *conn );
+
+int
+satip_rtsp_setup( satip_rtsp_connection_t *conn,
+                  int src, int fe, int udp_port,
+                  const dvb_mux_conf_t *dmc,
+                  int connection_close );
+
+int
+satip_rtsp_play( satip_rtsp_connection_t *sd, const char *pids,
+                 const char *addpids, const char *delpids );
+
+int
+satip_rtsp_teardown( satip_rtsp_connection_t *conn );
+
+#endif /* __TVH_SATIP_PRIVATE_H__ */
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
new file mode 100644 (file)
index 0000000..ab6574b
--- /dev/null
@@ -0,0 +1,580 @@
+/*
+ *  Tvheadend - SAT>IP DVB RTSP client
+ *
+ *  Copyright (C) 2014 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 <pthread.h>
+#include <signal.h>
+#include "tvheadend.h"
+#include "htsbuf.h"
+#include "tcp.h"
+#include "http.h"
+#include "satip_private.h"
+
+/*
+ *
+ */
+satip_rtsp_connection_t *
+satip_rtsp_connection( satip_device_t *sd )
+{
+  satip_rtsp_connection_t *conn;
+  char errbuf[256];
+
+  conn = calloc(1, sizeof(satip_rtsp_connection_t));
+  htsbuf_queue_init(&conn->wq2, 0);
+  conn->port = 554;
+  conn->timeout = 60;
+  conn->fd = tcp_connect(sd->sd_info.addr, conn->port,
+                         errbuf, sizeof(errbuf), 2);
+  if (conn->fd < 0) {
+    tvhlog(LOG_ERR, "satip", "RTSP - unable to connect - %s", errbuf);
+    free(conn);
+    return NULL;
+  }
+  conn->device = sd;
+  conn->ping_time = dispatch_clock;
+  return conn;
+}
+
+void
+satip_rtsp_connection_close( satip_rtsp_connection_t *conn )
+{
+  
+  htsbuf_queue_flush(&conn->wq2);
+  free(conn->session);
+  free(conn->header);
+  free(conn->data);
+  free(conn->wbuf);
+  if (conn->fd > 0)
+    close(conn->fd);
+  free(conn);
+}
+
+int
+satip_rtsp_send_partial( satip_rtsp_connection_t *conn )
+{
+  ssize_t r;
+
+  conn->sending = 1;
+  while (1) {
+    r = send(conn->fd, conn->wbuf + conn->wpos, conn->wsize - conn->wpos, MSG_DONTWAIT);
+    if (r < 0) {
+      if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+        continue;
+      return -errno;
+    }
+    conn->wpos += r;
+    if (conn->wpos >= conn->wsize) {
+      conn->sending = 0;
+      return 1;
+    }
+    break;
+  }
+  return 0;
+}
+
+int
+satip_rtsp_send( satip_rtsp_connection_t *conn, htsbuf_queue_t *q,
+                 satip_rtsp_cmd_t cmd )
+{
+  int r;
+
+  conn->ping_time = dispatch_clock;
+  conn->cmd = cmd;
+  free(conn->wbuf);
+  htsbuf_qprintf(q, "CSeq: %i\r\n\r\n", ++conn->cseq);
+  conn->wbuf    = htsbuf_to_string(q);
+  conn->wsize   = strlen(conn->wbuf);
+  conn->wpos    = 0;
+#if ENABLE_TRACE
+  tvhtrace("satip", "%s - sending RTSP cmd", conn->device->sd_info.addr);
+  tvhlog_hexdump("satip", conn->wbuf, conn->wsize);
+#endif
+  while (!(r = satip_rtsp_send_partial(conn))) ;
+  return r;
+}
+
+static int
+satip_rtsp_send2( satip_rtsp_connection_t *conn, htsbuf_queue_t *q,
+                  satip_rtsp_cmd_t cmd )
+{
+  conn->wq2_loaded = 1;
+  conn->wq2_cmd = cmd;
+  htsbuf_appendq(&conn->wq2, q);
+  return 1;
+}
+
+int
+satip_rtsp_receive( satip_rtsp_connection_t *conn )
+{
+  char buf[1024], *saveptr, *argv[3], *d, *p, *p1;
+  htsbuf_queue_t header, data;
+  int cseq_seen;
+  ssize_t r;
+
+  r = recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT);
+  if (r == 0)
+    return -ESTRPIPE;
+  if (r < 0) {
+    if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+      return 0;
+    return -errno;
+  }
+#if ENABLE_TRACE
+  if (r > 0) {
+    tvhtrace("satip", "%s - received RTSP answer", conn->device->sd_info.addr);
+    tvhlog_hexdump("satip", buf, r);
+  }
+#endif
+  if (r + conn->rsize >= sizeof(conn->rbuf))
+    return -EINVAL;
+  memcpy(conn->rbuf + conn->rsize, buf, r);
+  conn->rsize += r;
+  conn->rbuf[conn->rsize] = '\0';
+  if (conn->rsize > 3 &&
+      (d = strstr(conn->rbuf, "\r\n\r\n")) != NULL) {
+    *d = '\0';
+    htsbuf_queue_init(&header, 0);
+    htsbuf_queue_init(&data, 0);
+    p = strtok_r(conn->rbuf, "\r\n", &saveptr);
+    if (p == NULL)
+      goto fail;
+    tvhtrace("satip", "%s - RTSP answer '%s'", conn->device->sd_info.addr, p);
+    if (http_tokenize(p, argv, 3, -1) != 3)
+      goto fail;
+    if (strcmp(argv[0], "RTSP/1.0"))
+      goto fail;
+    if ((conn->code = atoi(argv[1])) <= 0)
+      goto fail;
+    cseq_seen = 0;
+    while ((p = strtok_r(NULL, "\r\n", &saveptr)) != NULL) {
+      p1 = strdup(p);
+      if (http_tokenize(p, argv, 2, ':') != 2)
+        goto fail;
+      if (strcmp(argv[0], "CSeq") == 0) {
+        cseq_seen = conn->cseq == atoi(argv[1]);
+      } else {
+        htsbuf_append(&header, p1, strlen(p1));
+        htsbuf_append(&header, "\n", 1);
+      }
+      free(p1);
+    }
+    if (!cseq_seen)
+      goto fail;
+    free(conn->header);
+    free(conn->data);
+    conn->header = htsbuf_to_string(&header);
+    conn->data   = htsbuf_to_string(&data);
+#if ENABLE_TRACE
+    tvhtrace("satip", "%s - received RTSP header", conn->device->sd_info.addr);
+    tvhlog_hexdump("satip", conn->header, strlen(conn->header));
+    if (strlen(conn->data)) {
+      tvhtrace("satip", "%s - received RTSP data", conn->device->sd_info.addr);
+      tvhlog_hexdump("satip", conn->data, strlen(conn->data));
+    }
+#endif
+    htsbuf_queue_flush(&header);
+    htsbuf_queue_flush(&data);
+    conn->rsize = 0;
+    /* second write */
+    if (conn->wq2_loaded && conn->code == 200) {
+      r =  satip_rtsp_send(conn, &conn->wq2, conn->wq2_cmd);
+      htsbuf_queue_flush(&conn->wq2);
+      conn->wq2_loaded = 0;
+      return r;
+    }
+    return 1;
+fail:
+    htsbuf_queue_flush(&header);
+    htsbuf_queue_flush(&data);
+    conn->rsize = 0;
+    return -EINVAL;
+  }
+  /* unfinished */
+  return 0;
+}
+
+/*
+ *
+ */
+
+int
+satip_rtsp_options_decode( satip_rtsp_connection_t *conn )
+{
+  char *argv[32], *s, *saveptr;
+  int i, n, what = 0;
+  
+  s = strtok_r(conn->header, "\n", &saveptr);
+  while (s) {
+    n = http_tokenize(s, argv, 32, ',');
+    if (strcmp(argv[0], "Public:") == 0)
+      for (i = 1; i < n; i++) {
+        if (strcmp(argv[i], "DESCRIBE") == 0)
+          what |= 1;
+        else if (strcmp(argv[i], "SETUP") == 0)
+          what |= 2;
+        else if (strcmp(argv[i], "PLAY") == 0)
+          what |= 4;
+        else if (strcmp(argv[i], "TEARDOWN") == 0)
+          what |= 8;
+      }
+    s = strtok_r(NULL, "\n", &saveptr);
+  }
+  return (conn->code != 200 && what != 0x0f) ? -1 : 1;
+}
+
+void
+satip_rtsp_options( satip_rtsp_connection_t *conn )
+{
+  htsbuf_queue_t q;
+  htsbuf_queue_init(&q, 0);
+  htsbuf_qprintf(&q,
+           "OPTIONS rtsp://%s/ RTSP/1.0\r\n",
+            conn->device->sd_info.addr);
+  satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_OPTIONS);
+  htsbuf_queue_flush(&q);
+}
+
+int
+satip_rtsp_setup_decode( satip_rtsp_connection_t *conn )
+{
+  char *argv[32], *s, *saveptr;
+  int i, n;
+
+  if (conn->code >= 400)
+    return -1;
+  if (conn->code != 200)
+    return 0;
+  conn->client_port = 0;
+  s = strtok_r(conn->header, "\n", &saveptr);
+  while (s) {
+    n = http_tokenize(s, argv, 32, ';');
+    if (strcmp(argv[0], "Session:") == 0) {
+      conn->session = strdup(argv[1]);
+      for (i = 2; i < n; i++) {
+        if (strncmp(argv[i], "timeout=", 8) == 0) {
+          conn->timeout = atoi(argv[i] + 8);
+          if (conn->timeout <= 20 || conn->timeout > 3600)
+            return -1;
+        }
+      }
+    } else if (strcmp(argv[0], "com.ses.streamID:") == 0) {
+      conn->stream_id = atoll(argv[1]);
+      /* zero is valid stream id per specification */
+      if (argv[1][0] == '0' && argv[1][0] == '\0')
+        conn->stream_id = 0;
+      else if (conn->stream_id <= 0)
+        return -1;
+    } else if (strcmp(argv[0], "Transport:") == 0) {
+      if (strcmp(argv[1], "RTP/AVP"))
+        return -1;
+      if (strcmp(argv[2], "unicast"))
+        return -1;
+      for (i = 2; i < n; i++) {
+        if (strncmp(argv[i], "client_port=", 12) == 0)
+          conn->client_port = atoi(argv[i] + 12);
+      }
+    }
+    s = strtok_r(NULL, "\n", &saveptr);
+  }
+  return 1;
+}
+
+typedef struct tvh2satip {
+  int         t; ///< TVH internal value
+  const char *s; ///< SATIP API value
+} tvh2satip_t;
+
+#define TABLE_EOD -1
+
+static const char *
+satip_rtsp_setup_find(const char *prefix, tvh2satip_t *tbl,
+                      int src, const char *defval)
+{
+  while (tbl->t >= 0) {
+    if (tbl->t == src)
+     return tbl->s;
+    tbl++;
+  }
+  tvhtrace("satip", "%s - cannot translate %d", prefix, src);
+  return defval;
+}
+
+#define ADD(s, d, def) \
+  strcat(buf, "&" #d "="), strcat(buf, satip_rtsp_setup_find(#d, d, dmc->s, def))
+
+static void
+satip_rtsp_add_val(const char *name, char *buf, uint32_t val)
+{
+  char sec[4];
+
+  sprintf(buf + strlen(buf), "&%s=%i", name, val / 1000);
+  if (val % 1000) {
+    sprintf(sec, ".%03i", val % 1000);
+    if (sec[3] == '0') {
+      sec[3] = '\0';
+      if (sec[2] == '0')
+        sec[2] = '\0';
+    }
+  }
+}
+
+int
+satip_rtsp_setup( satip_rtsp_connection_t *conn, int src, int fe,
+                  int udp_port, const dvb_mux_conf_t *dmc,
+                  int connection_close )
+{
+  static tvh2satip_t msys[] = {
+    { .t = DVB_SYS_DVBT,                      "dvbt"  },
+    { .t = DVB_SYS_DVBT2,                     "dvbt2" },
+    { .t = DVB_SYS_DVBS,                      "dvbs"  },
+    { .t = DVB_SYS_DVBS2,                     "dvbs2" },
+    { .t = TABLE_EOD }
+  };
+  static tvh2satip_t pol[] = {
+    { .t = DVB_POLARISATION_HORIZONTAL,       "h"     },
+    { .t = DVB_POLARISATION_VERTICAL,         "v"     },
+    { .t = DVB_POLARISATION_CIRCULAR_LEFT,    "l"     },
+    { .t = DVB_POLARISATION_CIRCULAR_RIGHT,   "r"     },
+    { .t = TABLE_EOD }
+  };
+  static tvh2satip_t ro[] = {
+    { .t = DVB_ROLLOFF_AUTO,                  "0.35"  },
+    { .t = DVB_ROLLOFF_20,                    "0.20"  },
+    { .t = DVB_ROLLOFF_25,                    "0.25"  },
+    { .t = DVB_ROLLOFF_35,                    "0.35"  },
+    { .t = TABLE_EOD }
+  };
+  static tvh2satip_t mtype[] = {
+    { .t = DVB_MOD_AUTO,                      "auto"  },
+    { .t = DVB_MOD_QAM_16,                    "16qam" },
+    { .t = DVB_MOD_QAM_32,                    "32qam" },
+    { .t = DVB_MOD_QAM_64,                    "64qam" },
+    { .t = DVB_MOD_QAM_128,                   "128qam"},
+    { .t = DVB_MOD_QAM_256,                   "256qam"},
+    { .t = DVB_MOD_QPSK,                      "qpsk"  },
+    { .t = DVB_MOD_PSK_8,                     "8psk"  },
+    { .t = TABLE_EOD }
+  };
+  static tvh2satip_t plts[] = {
+    { .t = DVB_PILOT_AUTO,                    "auto"  },
+    { .t = DVB_PILOT_ON,                      "on"    },
+    { .t = DVB_PILOT_OFF,                     "off"   },
+    { .t = TABLE_EOD }
+  };
+  static tvh2satip_t fec[] = {
+    { .t = DVB_FEC_AUTO,                      "auto"  },
+    { .t = DVB_FEC_1_2,                       "12"    },
+    { .t = DVB_FEC_2_3,                       "23"    },
+    { .t = DVB_FEC_3_4,                       "34"    },
+    { .t = DVB_FEC_3_5,                       "35"    },
+    { .t = DVB_FEC_4_5,                       "45"    },
+    { .t = DVB_FEC_5_6,                       "56"    },
+    { .t = DVB_FEC_7_8,                       "78"    },
+    { .t = DVB_FEC_8_9,                       "89"    },
+    { .t = DVB_FEC_9_10,                      "910"   },
+    { .t = TABLE_EOD }
+  };
+  static tvh2satip_t tmode[] = {
+    { .t = DVB_TRANSMISSION_MODE_AUTO,        "auto"  },
+    { .t = DVB_TRANSMISSION_MODE_1K,          "1k"    },
+    { .t = DVB_TRANSMISSION_MODE_2K,          "2k"    },
+    { .t = DVB_TRANSMISSION_MODE_4K,          "4k"    },
+    { .t = DVB_TRANSMISSION_MODE_8K,          "8k"    },
+    { .t = DVB_TRANSMISSION_MODE_16K,         "16k"   },
+    { .t = DVB_TRANSMISSION_MODE_32K,         "32k"   },
+    { .t = TABLE_EOD }
+  };
+  static tvh2satip_t gi[] = {
+    { .t = DVB_GUARD_INTERVAL_AUTO,           "auto"  },
+    { .t = DVB_GUARD_INTERVAL_1_4,            "14"    },
+    { .t = DVB_GUARD_INTERVAL_1_8,            "18"    },
+    { .t = DVB_GUARD_INTERVAL_1_16,           "116"   },
+    { .t = DVB_GUARD_INTERVAL_1_32,           "132"   },
+    { .t = DVB_GUARD_INTERVAL_1_128,          "1128"  },
+    { .t = DVB_GUARD_INTERVAL_19_128,         "19128" },
+    { .t = DVB_GUARD_INTERVAL_19_256,         "19256" },
+    { .t = TABLE_EOD }
+  };
+
+  char buf[512];
+  htsbuf_queue_t q;
+  int r;
+
+  htsbuf_queue_init(&q, 0);
+  if (src > 0)
+    sprintf(buf, "src=%i&", src);
+  else
+    buf[0] = '\0';
+  sprintf(buf + strlen(buf), "fe=%i", fe);
+  satip_rtsp_add_val("freq", buf, dmc->dmc_fe_freq);
+  if (dmc->dmc_fe_delsys == DVB_SYS_DVBS ||
+      dmc->dmc_fe_delsys == DVB_SYS_DVBS2) {
+    satip_rtsp_add_val("sr",   buf, dmc->u.dmc_fe_qpsk.symbol_rate);
+    ADD(dmc_fe_delsys,              msys,  "dvbs");
+    ADD(dmc_fe_modulation,          mtype, "qpsk");
+    ADD(u.dmc_fe_qpsk.polarisation, pol,   "h"   );
+    ADD(u.dmc_fe_qpsk.fec_inner,    fec,   "auto");
+    ADD(dmc_fe_rolloff,             ro,    "0.35");
+    if (dmc->dmc_fe_pilot != DVB_PILOT_AUTO)
+      ADD(dmc_fe_pilot,             plts,  "auto");
+  } else {
+    if (dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_AUTO &&
+        dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_NONE)
+      satip_rtsp_add_val("bw", buf, dmc->u.dmc_fe_ofdm.bandwidth);
+    ADD(dmc_fe_delsys, msys, "dvbt");
+    if (dmc->dmc_fe_modulation != DVB_MOD_AUTO &&
+        dmc->dmc_fe_modulation != DVB_MOD_NONE &&
+        dmc->dmc_fe_modulation != DVB_MOD_QAM_AUTO)
+      ADD(dmc_fe_modulation, mtype, "64qam");
+    if (dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_AUTO &&
+        dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_NONE)
+      ADD(u.dmc_fe_ofdm.transmission_mode, tmode, "8k");
+    if (dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_AUTO &&
+        dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_NONE)
+      ADD(u.dmc_fe_ofdm.guard_interval, gi, "18");
+  }
+  tvhtrace("satip", "setup params - %s", buf);
+  if (conn->stream_id > 0)
+    htsbuf_qprintf(&q, "SETUP rtsp://%s/stream=%li?",
+                   conn->device->sd_info.addr, conn->stream_id);
+  else
+    htsbuf_qprintf(&q, "SETUP rtsp://%s/?", conn->device->sd_info.addr);
+  htsbuf_qprintf(&q,
+      "%s RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port=%i-%i\r\n",
+      buf, udp_port, udp_port+1);
+  if (conn->session)
+    htsbuf_qprintf(&q, "Session: %s\r\n", conn->session);
+  if (connection_close)
+    htsbuf_qprintf(&q, "Connection: close\r\n");
+  r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_SETUP);
+  htsbuf_queue_flush(&q);
+  return r;
+}
+
+static const char *
+satip_rtsp_pids_strip( satip_rtsp_connection_t *conn, const char *s )
+{
+  int maxlen = conn->device->sd_pids_len;
+  char *ptr;
+
+  if (s == NULL)
+    return NULL;
+  while (*s == ',')
+    s++;
+  while (strlen(s) > maxlen) {
+    ptr = rindex(s, ',');
+    if (ptr == NULL)
+      abort();
+    *ptr = '\0';
+  }
+  if (*s == '\0')
+    return NULL;
+  return s;
+}
+
+int
+satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids,
+                 const char *addpids, const char *delpids )
+{
+  htsbuf_queue_t q;
+  int r, split = 0;
+
+  pids    = satip_rtsp_pids_strip(conn, pids);
+  addpids = satip_rtsp_pids_strip(conn, addpids);
+  delpids = satip_rtsp_pids_strip(conn, delpids);
+
+  if (pids == NULL && addpids == NULL && delpids == NULL)
+    return 1;
+
+  // printf("pids = '%s' addpids = '%s' delpids = '%s'\n", pids, addpids, delpids);
+
+  htsbuf_queue_init(&q, 0);
+  htsbuf_qprintf(&q, "PLAY rtsp://%s/stream=%li?",
+                 conn->device->sd_info.addr, conn->stream_id);
+  /* pids setup and add/del requests cannot be mixed per specification */
+  if (pids) {
+    htsbuf_qprintf(&q, "pids=%s", pids);
+  } else {
+    if (delpids)
+      htsbuf_qprintf(&q, "delpids=%s", delpids);
+    if (addpids) {
+      if (delpids) {
+        /* try to maintain the maximum request size - simple split */
+        if (strlen(addpids) + strlen(delpids) <= conn->device->sd_pids_len)
+          split = 1;
+        else
+          htsbuf_append(&q, "&", 1);
+      }
+      if (!split)
+        htsbuf_qprintf(&q, "addpids=%s", addpids);
+    }
+  }
+  htsbuf_qprintf(&q, " RTSP/1.0\r\nSession: %s\r\n", conn->session);
+  r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_PLAY);
+  htsbuf_queue_flush(&q);
+  if (r || !split)
+    return r;
+
+  htsbuf_queue_init(&q, 0);
+  htsbuf_qprintf(&q, "PLAY rtsp://%s/stream=%li?",
+                 conn->device->sd_info.addr, conn->stream_id);
+  htsbuf_qprintf(&q, "addpids=%s", addpids);
+  htsbuf_qprintf(&q, " RTSP/1.0\r\nSession: %s\r\n", conn->session);
+  r = satip_rtsp_send2(conn, &q, SATIP_RTSP_CMD_PLAY);
+  htsbuf_queue_flush(&q);
+  return r;
+}
+
+int
+satip_rtsp_teardown( satip_rtsp_connection_t *conn )
+{
+  int r;
+  htsbuf_queue_t q;
+  htsbuf_queue_init(&q, 0);
+  htsbuf_qprintf(&q,
+           "TEARDOWN rtsp://%s/stream=%li RTSP/1.0\r\nSession: %s\r\n",
+            conn->device->sd_info.addr, conn->stream_id, conn->session);
+  r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_TEARDOWN);
+  htsbuf_queue_flush(&q);
+  return r;
+}
+
+#if 0
+static int
+satip_rtsp_describe_decode
+  ( satip_connection_t *conn )
+{
+  if (header == NULL)
+    return 1;
+  printf("describe: %i\n", conn->code);
+  printf("header:\n%s\n",  conn->header);
+  printf("data:\n%s\n",    conn->data);
+  return 0;
+}
+
+static void
+satip_rtsp_describe( satip_connection_t *conn )
+{
+  htsbuf_queue_t q;
+  htsbuf_queue_init(&q, 0);
+  htsbuf_qprintf(&q,
+           "DESCRIBE rtsp://%s/ RTSP/1.0\r\n", sd->sd_info.addr);
+  satip_rtsp_write(conn, &q);
+  htsbuf_queue_flush(&q);
+}
+#endif
diff --git a/src/input/mpegts/satip/satip_satconf.c b/src/input/mpegts/satip/satip_satconf.c
new file mode 100644 (file)
index 0000000..20b47c6
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ *  Tvheadend - SAT>IP DVB satconf
+ *
+ *  Copyright (C) 2014 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 "satip_private.h"
+#include "settings.h"
+
+/* **************************************************************************
+ * Frontend callbacks
+ * *************************************************************************/
+
+static satip_satconf_t *
+satip_satconf_find_ele( satip_frontend_t *lfe, mpegts_mux_t *mux )
+{
+  satip_satconf_t *sfc;
+  TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link) {
+    if (idnode_set_exists(sfc->sfc_networks, &mux->mm_network->mn_id))
+      return sfc;
+  }
+  return NULL;
+}
+
+int
+satip_satconf_get_priority
+  ( satip_frontend_t *lfe, mpegts_mux_t *mm )
+{
+  satip_satconf_t *sfc = satip_satconf_find_ele(lfe, mm);
+  return sfc->sfc_priority;
+}
+
+int
+satip_satconf_get_position
+  ( satip_frontend_t *lfe, mpegts_mux_t *mm )
+{
+  satip_satconf_t *sfc = satip_satconf_find_ele(lfe, mm);
+  return sfc->sfc_position;
+}
+
+/* **************************************************************************
+ * Class definition
+ * *************************************************************************/
+
+static const void *
+satip_satconf_class_network_get( void *o )
+{
+  satip_satconf_t *sfc  = o;
+  return idnode_set_as_htsmsg(sfc->sfc_networks);
+}
+
+static int
+satip_satconf_class_network_set( void *o, const void *p )
+{
+  satip_satconf_t *sfc  = o;
+  const htsmsg_t *msg = p;
+  mpegts_network_t *mn;
+  idnode_set_t *n = idnode_set_create();
+  htsmsg_field_t *f;
+  const char *str;
+  int i, save;
+
+  HTSMSG_FOREACH(f, msg) {
+    if (!(str = htsmsg_field_get_str(f))) continue;
+    if (!(mn = mpegts_network_find(str))) continue;
+    idnode_set_add(n, &mn->mn_id, NULL);
+  }
+
+  save = n->is_count != sfc->sfc_networks->is_count;
+  if (!save) {
+    for (i = 0; i < n->is_count; i++)
+      if (!idnode_set_exists(sfc->sfc_networks, n->is_array[i])) {
+        save = 1;
+        break;
+      }
+  }
+  if (save) {
+    /* update the local (antenna satconf) network list */
+    idnode_set_free(sfc->sfc_networks);
+    sfc->sfc_networks = n;
+    /* update the input (frontend) network list */
+    htsmsg_t *l = htsmsg_create_list();
+    satip_frontend_t *lfe = sfc->sfc_lfe;
+    satip_satconf_t *sfc2;
+    TAILQ_FOREACH(sfc2, &lfe->sf_satconf, sfc_link) {
+      for (i = 0; i < sfc2->sfc_networks->is_count; i++)
+        htsmsg_add_str(l, NULL,
+                       idnode_uuid_as_str(sfc2->sfc_networks->is_array[i]));
+    }
+    mpegts_input_class_network_set(lfe, l);
+    htsmsg_destroy(l);
+  } else {
+    idnode_set_free(n);
+  }
+  return save;
+}
+
+static htsmsg_t *
+satip_satconf_class_network_enum( void *o )
+{
+  htsmsg_t *m = htsmsg_create_map();
+  htsmsg_t *p = htsmsg_create_map();
+  htsmsg_add_str(m, "type",  "api");
+  htsmsg_add_str(m, "uri",   "idnode/load");
+  htsmsg_add_str(m, "event", "mpegts_network");
+  htsmsg_add_u32(p, "enum",  1);
+  htsmsg_add_str(p, "class", dvb_network_dvbs_class.ic_class);
+  htsmsg_add_msg(m, "params", p);
+
+  return m;
+}
+
+static char *
+satip_satconf_class_network_rend( void *o )
+{
+  satip_satconf_t *sfc  = o;
+  htsmsg_t               *l   = idnode_set_as_htsmsg(sfc->sfc_networks);
+  char                   *str = htsmsg_list_2_csv(l);
+  htsmsg_destroy(l);
+  return str;
+}
+
+static const char *
+satip_satconf_class_get_title ( idnode_t *o )
+{
+  return ((satip_satconf_t *)o)->sfc_name;
+}
+
+static void
+satip_satconf_class_save ( idnode_t *in )
+{
+  satip_satconf_t *sfc = (satip_satconf_t*)in;
+  satip_device_save(sfc->sfc_lfe->sf_device);
+}
+
+const idclass_t satip_satconf_class =
+{
+  .ic_class      = "satip_satconf",
+  .ic_caption    = "Satconf",
+  .ic_get_title  = satip_satconf_class_get_title,
+  .ic_save       = satip_satconf_class_save,
+  .ic_properties = (const property_t[]) {
+    {
+      .type     = PT_BOOL,
+      .id       = "enabled",
+      .name     = "Enabled",
+      .off      = offsetof(satip_satconf_t, sfc_enabled),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "displayname",
+      .name     = "Name",
+      .off      = offsetof(satip_satconf_t, sfc_name),
+      .notify   = idnode_notify_title_changed,
+    },
+    {
+      .type     = PT_INT,
+      .id       = "priority",
+      .name     = "Priority",
+      .off      = offsetof(satip_satconf_t, sfc_priority),
+      .opts     = PO_ADVANCED,
+    },
+    {
+      .type     = PT_INT,
+      .id       = "position",
+      .name     = "Position",
+      .off      = offsetof(satip_satconf_t, sfc_position),
+      .def.i    = 1,
+      .opts     = PO_RDONLY | PO_ADVANCED,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "networks",
+      .name     = "Networks",
+      .islist   = 1,
+      .set      = satip_satconf_class_network_set,
+      .get      = satip_satconf_class_network_get,
+      .list     = satip_satconf_class_network_enum,
+      .rend     = satip_satconf_class_network_rend,
+    },
+    {}
+  }
+};
+
+/* **************************************************************************
+ * Creation/Config
+ * *************************************************************************/
+
+static satip_satconf_t *
+satip_satconf_create0
+  ( satip_frontend_t *lfe, htsmsg_t *conf, int position )
+{
+  static const char *tbl[] = {" (AA)", " (AB)", " (BA)", " (BB)"};
+  const char *uuid = NULL;
+  satip_satconf_t *sfc = calloc(1, sizeof(*sfc));
+  char buf[32];
+  const char *s;
+
+  /* defaults */
+  sfc->sfc_priority = 1;
+
+  if (conf)
+    uuid = htsmsg_get_str(conf, "uuid");
+  if (idnode_insert(&sfc->sfc_id, uuid, &satip_satconf_class)) {
+    free(sfc);
+    return NULL;
+  }
+  sfc->sfc_networks = idnode_set_create();
+  sfc->sfc_lfe      = lfe;
+  sfc->sfc_position = position + 1;
+  TAILQ_INSERT_TAIL(&lfe->sf_satconf, sfc, sfc_link);
+  if (conf)
+    idnode_load(&sfc->sfc_id, conf);
+  if (sfc->sfc_name == NULL || sfc->sfc_name[0] == '\0') {
+    free(sfc->sfc_name);
+    s = position < 4 ? tbl[position] : "";
+    snprintf(buf, sizeof(buf), "Position #%i%s", position + 1, s);
+    sfc->sfc_name = strdup(buf);
+  }
+
+  return sfc;
+}
+
+void
+satip_satconf_create
+  ( satip_frontend_t *lfe, htsmsg_t *conf )
+{
+  htsmsg_t *l, *e;
+  htsmsg_field_t *f;
+  int pos = 0;
+
+  if (conf && (l = htsmsg_get_list(conf, "satconf"))) {
+    satip_satconf_destroy(lfe);
+    HTSMSG_FOREACH(f, l) {
+      if (!(e = htsmsg_field_get_map(f))) continue;
+      if (satip_satconf_create0(lfe, e, pos++))
+        lfe->sf_positions++;
+    }
+  }
+
+  if (lfe->sf_positions == 0)
+    for ( ; lfe->sf_positions < 4; lfe->sf_positions++)
+      satip_satconf_create0(lfe, NULL, lfe->sf_positions);
+}
+
+static void
+satip_satconf_destroy0
+  ( satip_satconf_t *sfc )
+{
+  satip_frontend_t *lfe = sfc->sfc_lfe;
+  TAILQ_REMOVE(&lfe->sf_satconf, sfc, sfc_link);
+  idnode_unlink(&sfc->sfc_id);
+  idnode_set_free(sfc->sfc_networks);
+  free(sfc->sfc_name);
+  free(sfc);
+}
+
+void
+satip_satconf_updated_positions
+  ( satip_frontend_t *lfe )
+{
+  satip_satconf_t *sfc, *sfc_old;
+  int i;
+
+  sfc = TAILQ_FIRST(&lfe->sf_satconf);
+  for (i = 0; i < lfe->sf_positions; i++) {
+    if (sfc == NULL)
+      satip_satconf_create0(lfe, NULL, i);
+    sfc = sfc ? TAILQ_NEXT(sfc, sfc_link) : NULL;
+  }
+  while (sfc) {   
+    sfc_old = sfc;
+    sfc = TAILQ_NEXT(sfc, sfc_link);
+    satip_satconf_destroy0(sfc_old);
+  }
+}
+
+void
+satip_satconf_destroy ( satip_frontend_t *lfe )
+{
+  satip_satconf_t *sfc;
+
+  while ((sfc = TAILQ_FIRST(&lfe->sf_satconf)) != NULL)
+    satip_satconf_destroy0(sfc);
+  lfe->sf_positions = 0;
+}
+
+void
+satip_satconf_save ( satip_frontend_t *lfe, htsmsg_t *m )
+{
+  satip_satconf_t *sfc;
+  htsmsg_t *l, *e;
+
+  l = htsmsg_create_list();
+  TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link) { 
+    e = htsmsg_create_map();
+    idnode_save(&sfc->sfc_id, e);
+    htsmsg_add_msg(l, NULL, e);
+  }
+  htsmsg_add_msg(m, "satconf", l);
+}
+
+
+/******************************************************************************
+ * Editor Configuration
+ *
+ * vim:sts=2:ts=2:sw=2:et
+ *****************************************************************************/
index 5f356381def988993b08d51a8dd236a57ca7c7b5..e8e2ff8d9ccc15b89e98b9593e908ade76f37bee 100644 (file)
@@ -134,6 +134,9 @@ const tvh_caps_t tvheadend_capabilities[] = {
 #if ENABLE_LINUXDVB
   { "linuxdvb", NULL },
 #endif
+#if ENABLE_SATIP_CLIENT
+  { "satip_client", NULL },
+#endif
 #if ENABLE_LIBAV
   { "transcoding", &transcoding_enabled },
 #endif