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 \
"cwc:yes"
"v4l:no"
"linuxdvb:yes"
+ "satip_client:yes"
"iptv:yes"
"tsfile:yes"
"dvbscan:yes"
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
#
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 );
#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__ */
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();
#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
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
+ *****************************************************************************/
#if ENABLE_LINUXDVB
{ "linuxdvb", NULL },
#endif
+#if ENABLE_SATIP_CLIENT
+ { "satip_client", NULL },
+#endif
#if ENABLE_LIBAV
{ "transcoding", &transcoding_enabled },
#endif