Пример #1
0
int
rtsp_options_decode( http_client_t *hc )
{
  char *argv[32], *p;
  int i, n, what = 0;

  p = http_arg_get(&hc->hc_args, "Public");
  if (p == NULL)
    return -EIO;
  n = http_tokenize(p, argv, 32, ',');
  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;
  }
  return (hc->hc_code != 200 && what != 0x0f) ? -EIO : HTTP_CON_OK;
}
Пример #2
0
/*
 * Connected
 */
static int
iptv_http_header ( http_client_t *hc )
{
  iptv_mux_t *im = hc->hc_aux;
  char *argv[3], *s;
  int n;

  if (hc->hc_aux == NULL)
    return 0;

  /* multiple headers for redirections */
  if (hc->hc_code != HTTP_STATUS_OK)
    return 0;

  s = http_arg_get(&hc->hc_args, "Content-Type");
  if (s) {
    n = http_tokenize(s, argv, ARRAY_SIZE(argv), ';');
    if (n > 0 &&
        (strcasecmp(s, "audio/mpegurl") == 0 ||
         strcasecmp(s, "audio/x-mpegurl") == 0 ||
         strcasecmp(s, "application/x-mpegurl") == 0 ||
         strcasecmp(s, "application/apple.vnd.mpegurl") == 0 ||
         strcasecmp(s, "application/vnd.apple.mpegurl") == 0)) {
      if (im->im_m3u_header > 10) {
        im->im_m3u_header = 0;
        return 0;
      }
      im->im_m3u_header++;
      return 0;
    }
  }

  im->im_m3u_header = 0;
  pthread_mutex_lock(&global_lock);
  iptv_input_mux_started(hc->hc_aux);
  pthread_mutex_unlock(&global_lock);
  return 0;
}
Пример #3
0
int
rtsp_setup_decode( http_client_t *hc, int satip )
{
  char *argv[32], *argv2[2], *p;
  int i, n, j;

#if 0
  { http_arg_t *ra;
  TAILQ_FOREACH(ra, &hc->hc_args, link)
    printf("  %s: %s\n", ra->key, ra->val); }
#endif
  rtsp_clear_session(hc);
  if (hc->hc_code != 200)
    return -EIO;
  p = http_arg_get(&hc->hc_args, "Session");
  if (p == NULL)
    return -EIO;
  n = http_tokenize(p, argv, 32, ';');
  if (n < 1)
    return -EIO;
  hc->hc_rtsp_session = strdup(argv[0]);
  for (i = 1; i < n; i++) {
    if (strncasecmp(argv[i], "timeout=", 8) == 0) {
      hc->hc_rtp_timeout = atoi(argv[i] + 8);
      if (hc->hc_rtp_timeout < 20 || hc->hc_rtp_timeout > 3600) {
        tvhwarn(LS_RTSP, "timeout value out of range 20-3600 (%i)", hc->hc_rtp_timeout);
        return -EIO;
      }
    }
  }
  if (satip) {
    p = http_arg_get(&hc->hc_args, "com.ses.streamID");
    if (p == NULL)
      return -EIO;
    /* zero is valid stream id per specification */
    while (*p && ((*p == '0' && *(p + 1) == '0') || *p < ' '))
      p++;
    if (p[0] == '0' && p[1] == '\0') {
      hc->hc_rtsp_stream_id = 0;
    } else {
      hc->hc_rtsp_stream_id = atoll(p);
      if (hc->hc_rtsp_stream_id <= 0)
        return -EIO;
    }
  }
  p = http_arg_get(&hc->hc_args, "Transport");
  if (p == NULL)
    return -EIO;
  n = http_tokenize(p, argv, 32, ';');
  if (n < 2)
    return -EIO;
  hc->hc_rtp_tcp = -1;
  hc->hc_rtcp_tcp = -1;
  hc->hc_rtp_port = -1;
  hc->hc_rtcp_port = -1;
  if (!strcasecmp(argv[0], "RTP/AVP/TCP")) {
    for (i = 1; i < n; i++) {
      if (strncmp(argv[i], "interleaved=", 12) == 0) {
        j = http_tokenize(argv[i] + 12, argv2, 2, '-');
        if (j > 0) {
          hc->hc_rtp_tcp = atoi(argv2[0]);
          if (hc->hc_rtp_tcp < 0)
            return -EIO;
          if (j > 1) {
            hc->hc_rtcp_tcp = atoi(argv2[1]);
            if (hc->hc_rtcp_tcp < 0)
              return -EIO;
          }
        } else {
          return -EIO;
        }
      }
    }
  } else if (!strcasecmp(argv[0], "RTP/AVP") ||
             !strcasecmp(argv[0], "RTP/AVP/UDP")) {
    if (n < 3)
      return -EIO;
    hc->hc_rtp_multicast = strcasecmp(argv[1], "multicast") == 0;
    if (strcasecmp(argv[1], "unicast") && !hc->hc_rtp_multicast)
      return -EIO;
    for (i = 2; i < n; i++) {
      if (strncmp(argv[i], "destination=", 12) == 0)
        hc->hc_rtp_dest = strdup(argv[i] + 12);
      else if (strncmp(argv[i], "client_port=", 12) == 0) {
        j = http_tokenize(argv[i] + 12, argv2, 2, '-');
        if (j > 0) {
          hc->hc_rtp_port = atoi(argv2[0]);
          if (hc->hc_rtp_port <= 0)
            return -EIO;
          if (j > 1) {
            hc->hc_rtcp_port = atoi(argv2[1]);
            if (hc->hc_rtcp_port <= 0)
              return -EIO;
          }
        } else {
          return -EIO;
        }
      }
      else if (strncmp(argv[i], "server_port=", 12) == 0) {
        j = http_tokenize(argv[i] + 12, argv2, 2, '-');
        if (j > 1) {
          hc->hc_rtcp_server_port = atoi(argv2[1]);
          if (hc->hc_rtcp_server_port <= 0)
            return -EIO;
        } else {
          return -EIO;
        }
      }
    }
  } else {
    return -EIO;
  }
  return HTTP_CON_OK;
}
Пример #4
0
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, ':') == 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, ARRAY_SIZE(argv), ':');
        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"))
    goto add_uuid;
  if (uuid == NULL || strlen(uuid) < 16 || satip_server_match_uuid(uuid))
    goto add_uuid;
  if (location == NULL || strncmp(location, "http://", 7))
    goto add_uuid;
  if (bootid == NULL || configid == NULL || server == NULL)
    goto add_uuid;

  /* Forward information to next layer */

  d = calloc(1, sizeof(satip_discovery_t));
  if (inet_ntop(conn->ip.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);
  return;

add_uuid:
  if (deviceid == NULL || uuid == NULL)
    return;
  /* if new uuid was discovered, retrigger MSEARCH */
  pthread_mutex_lock(&global_lock);
  if (!satip_device_find(uuid))
    gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 5);
  pthread_mutex_unlock(&global_lock);
}
Пример #5
0
static void
satip_discovery_http_closed(http_client_t *hc, int errn)
{
  satip_discovery_t *d = hc->hc_aux;
  char *s;
  htsmsg_t *xml = NULL, *tags, *root, *device;
  const char *friendlyname, *manufacturer, *manufacturerURL, *modeldesc;
  const char *modelname, *modelnum, *serialnum;
  const char *presentation, *tunercfg, *udn, *uuid;
  const char *cs, *arg;
  satip_device_info_t info;
  char errbuf[100];
  char *argv[10];
  int i, n;

  s = http_arg_get(&hc->hc_args, "Content-Type");
  if (s) {
    n = http_tokenize(s, argv, ARRAY_SIZE(argv), ';');
    if (n <= 0 || strcasecmp(s, "text/xml")) {
      errn = ENOENT;
      s = NULL;
    }
  }
  if (errn != 0 || s == NULL || hc->hc_code != 200 ||
      hc->hc_data_size == 0 || hc->hc_data == NULL) {
    tvhlog(LOG_ERR, "satip", "Cannot get %s: %s", d->location, strerror(errn));
    return;
  }

  if (tvhtrace_enabled()) {
    tvhtrace("satip", "received XML description from %s", hc->hc_host);
    tvhlog_hexdump("satip", hc->hc_data, hc->hc_data_size);
  }

  if (d->myaddr == NULL || d->myaddr[0] == '\0') {
    struct sockaddr_storage ip;
    socklen_t addrlen = sizeof(ip);
    errbuf[0] = '\0';
    getsockname(hc->hc_fd, (struct sockaddr *)&ip, &addrlen);
    inet_ntop(ip.ss_family, IP_IN_ADDR(ip), errbuf, sizeof(errbuf));
    free(d->myaddr);
    d->myaddr = strdup(errbuf);
  }

  s = hc->hc_data + hc->hc_data_size - 1;
  while (s != hc->hc_data && *s != '/')
    s--;
  if (s != hc->hc_data)
    s--;
  if (strncmp(s, "</root>", 7))
    return;
  /* Parse */
  xml = htsmsg_xml_deserialize(hc->hc_data, errbuf, sizeof(errbuf));
  hc->hc_data = 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)
    manufacturerURL = "";
  if ((modeldesc    = htsmsg_xml_get_cdata_str(device, "modelDescription")) == NULL)
    modeldesc = "";
  if ((modelname    = htsmsg_xml_get_cdata_str(device, "modelName")) == NULL)
    goto finish;
  if ((modelnum     = htsmsg_xml_get_cdata_str(device, "modelNumber")) == NULL)
    modelnum = "";
  if ((serialnum    = htsmsg_xml_get_cdata_str(device, "serialNumber")) == NULL)
    serialnum = "";
  if ((presentation = htsmsg_xml_get_cdata_str(device, "presentationURL")) == NULL)
    presentation = "";
  if ((udn          = htsmsg_xml_get_cdata_str(device, "UDN")) == NULL)
    goto finish;
  if ((tunercfg     = htsmsg_xml_get_cdata_str(device, "urn:ses-com:satipX_SATIPCAP")) == NULL)
    tunercfg = "";

  uuid = NULL;
  n = http_tokenize((char *)udn, argv, ARRAY_SIZE(argv), ':');
  for (i = 0; i < n+1; i++)
    if (argv[i] && strcmp(argv[i], "uuid") == 0) {
      uuid = argv[++i];
      break;
    }
  if (uuid == NULL || (d->uuid[0] && strcmp(uuid, d->uuid)))
    goto finish;

  info.rtsp_port = 554;
  info.srcs = 4;

  arg = http_arg_get(&hc->hc_args, "X-SATIP-RTSP-Port");
  if (arg) {
    i = atoi(arg);
    if (i > 0 && i < 65535)
      info.rtsp_port = i;
  }
  arg = http_arg_get(&hc->hc_args, "X-SATIP-Sources");
  if (arg) {
    i = atoi(arg);
    if (i > 0 && i < 128)
      info.srcs = i;
  }

  info.myaddr = strdup(d->myaddr);
  info.addr = strdup(d->url.host);
  info.uuid = strdup(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);
}
Пример #6
0
static satip_device_t *
satip_device_create( satip_device_info_t *info )
{
  satip_device_t *sd = calloc(1, sizeof(satip_device_t));
  tvh_uuid_t uuid;
  htsmsg_t *conf = NULL, *feconf = NULL;
  char *argv[10], *tunercfg;
  int i, j, n, m, fenum, v2, save = 0;
  dvb_fe_type_t type;
  char buf2[60];

  sd->sd_inload = 1;

  satip_device_calc_uuid(&uuid, info->uuid);

  conf = hts_settings_load("input/satip/adapters/%s", uuid.hex);

  /* some sane defaults */
  sd->sd_fast_switch = 1;
  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;
  sd->sd_dbus_allow  = 1;


  if (!tvh_hardware_create0((tvh_hardware_t*)sd, &satip_device_class,
                            uuid.hex, conf)) {
    /* Note: sd is freed in above fcn */
    return NULL;
  }

  pthread_mutex_init(&sd->sd_tune_mutex, 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
  sd->sd_info.rtsp_port = info->rtsp_port;
  sd->sd_info.srcs = info->srcs;

  /*
   * device specific hacks
   */
  satip_device_hack(sd);

  if (conf)
    feconf = htsmsg_get_map(conf, "frontends");
  save = !conf || !feconf;

  tunercfg = sd->sd_tunercfg;
  if (tunercfg == NULL)
    tunercfg = sd->sd_tunercfg = strdup("Auto");
  if (strncmp(tunercfg, "DVB", 3) && strncmp(tunercfg, "ATSC", 4))
    tunercfg = sd->sd_info.tunercfg;

  n = http_tokenize(tvh_strdupa(tunercfg), argv, 10, ',');
  for (i = m = 0, fenum = 1; i < n; i++) {
    type = DVB_TYPE_NONE;
    v2 = 0;
    if (strncmp(argv[i], "DVBS2-", 6) == 0) {
      type = DVB_TYPE_S;
      m = atoi(argv[i] + 6);
      v2 = 1;
    } else if (strncmp(argv[i], "DVBS-", 5) == 0) {
      type = DVB_TYPE_S;
      m = atoi(argv[i] + 5);
    } else if (strncmp(argv[i], "DVBT2-", 6) == 0) {
      type = DVB_TYPE_T;
      m = atoi(argv[i] + 6);
      v2 = 1;
    } else if (strncmp(argv[i], "DVBT-", 5) == 0) {
      type = DVB_TYPE_T;
      m = atoi(argv[i] + 5);
    } else if (strncmp(argv[i], "DVBC2-", 6) == 0) {
      type = DVB_TYPE_C;
      m = atoi(argv[i] + 6);
      v2 = 1;
    } else if (strncmp(argv[i], "DVBC-", 5) == 0) {
      type = DVB_TYPE_C;
      m = atoi(argv[i] + 5);
    } else if (strncmp(argv[i], "ATSC-", 5) == 0) {
      type = DVB_TYPE_ATSC;
      m = atoi(argv[i] + 5);
    } else if (strncmp(argv[i], "DVBCB-", 6) == 0) {
      m = atoi(argv[i] + 6);
      v2 = 2;
    }
    if (type == DVB_TYPE_NONE) {
      tvhlog(LOG_ERR, "satip", "%s: bad tuner type [%s]",
             satip_device_nicename(sd, buf2, sizeof(buf2)), argv[i]);
    } else if (m < 0 || m > 32) {
      tvhlog(LOG_ERR, "satip", "%s: bad tuner count [%s]",
             satip_device_nicename(sd, buf2, sizeof(buf2)), argv[i]);
    } else {
      sd->sd_nosave = 1;
      for (j = 0; j < m; j++)
        if (satip_frontend_create(feconf, sd, type, v2, fenum))
          fenum++;
      sd->sd_nosave = 0;
    }
  }

  if (save)
    satip_device_save(sd);

  sd->sd_inload = 0;

  htsmsg_destroy(conf);

  satip_device_dbus_notify(sd, "start");

  return sd;
}
Пример #7
0
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;
  }

  pthread_mutex_init(&sd->sd_tune_mutex, 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);
    } else if (strncmp(argv[i], "DVBC-", 5) == 0) {
      type = DVB_TYPE_C;
      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;
}