From 8583936c715b10a86b8c5d1cfb9465fc9e0d83b9 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 9 Apr 2014 19:52:23 +0200 Subject: [PATCH] Add SAT>IP support (remote network tuners) --- Makefile | 7 + configure | 12 + src/api.h | 1 + src/input.h | 3 + src/input/mpegts.c | 8 + src/input/mpegts/satip/satip.c | 818 +++++++++++++++++ src/input/mpegts/satip/satip.h | 26 + src/input/mpegts/satip/satip_frontend.c | 1120 +++++++++++++++++++++++ src/input/mpegts/satip/satip_private.h | 279 ++++++ src/input/mpegts/satip/satip_rtsp.c | 580 ++++++++++++ src/input/mpegts/satip/satip_satconf.c | 322 +++++++ src/main.c | 3 + 12 files changed, 3179 insertions(+) create mode 100644 src/input/mpegts/satip/satip.c create mode 100644 src/input/mpegts/satip/satip.h create mode 100644 src/input/mpegts/satip/satip_frontend.c create mode 100644 src/input/mpegts/satip/satip_private.h create mode 100644 src/input/mpegts/satip/satip_rtsp.c create mode 100644 src/input/mpegts/satip/satip_satconf.c diff --git a/Makefile b/Makefile index bfd2da08..e0bb0f6c 100644 --- a/Makefile +++ b/Makefile @@ -209,6 +209,13 @@ SRCS-${CONFIG_LINUXDVB} += \ src/input/mpegts/linuxdvb/linuxdvb_rotor.c \ src/input/mpegts/linuxdvb/linuxdvb_en50494.c +# SATIP +SRCS-${CONFIG_SATIP_CLIENT} += \ + src/input/mpegts/satip/satip.c \ + src/input/mpegts/satip/satip_frontend.c \ + src/input/mpegts/satip/satip_satconf.c \ + src/input/mpegts/satip/satip_rtsp.c + # IPTV SRCS-${CONFIG_IPTV} += \ src/input/mpegts/iptv/iptv.c \ diff --git a/configure b/configure index 00d302a7..839e53c9 100755 --- a/configure +++ b/configure @@ -19,6 +19,7 @@ OPTIONS=( "cwc:yes" "v4l:no" "linuxdvb:yes" + "satip_client:yes" "iptv:yes" "tsfile:yes" "dvbscan:yes" @@ -174,6 +175,17 @@ if enabled_or_auto curl; then fi fi +# +# SAT>IP client +# +if enabled_or_auto satip_client; then + if enabled curl; then + enable upnp + else + die "SAT>IP client requires curl enabled" + fi +fi + # # uriparser # diff --git a/src/api.h b/src/api.h index 629330e8..6a275732 100644 --- a/src/api.h +++ b/src/api.h @@ -59,6 +59,7 @@ void api_init ( void ); void api_done ( void ); void api_idnode_init ( void ); void api_input_init ( void ); +void api_input_satip_init ( void ); void api_service_init ( void ); void api_channel_init ( void ); void api_mpegts_init ( void ); diff --git a/src/input.h b/src/input.h index 6de228f9..1597236f 100644 --- a/src/input.h +++ b/src/input.h @@ -128,6 +128,9 @@ void tvh_input_stream_destroy ( tvh_input_stream_t *st ); #if ENABLE_LINUXDVB #include "input/mpegts/linuxdvb.h" #endif +#if ENABLE_SATIP_CLIENT +#include "input/mpegts/satip/satip.h" +#endif #endif #endif /* __TVH_INPUT_H__ */ diff --git a/src/input/mpegts.c b/src/input/mpegts.c index 8f2a0a5c..51117107 100644 --- a/src/input/mpegts.c +++ b/src/input/mpegts.c @@ -51,6 +51,11 @@ mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners ) linuxdvb_init(linuxdvb_mask); #endif + /* SAT>IP DVB client */ +#if ENABLE_SATIP_CLIENT + satip_init(); +#endif + /* Mux schedulers */ #if ENABLE_MPEGTS mpegts_mux_sched_init(); @@ -71,6 +76,9 @@ mpegts_done ( void ) #if ENABLE_LINUXDVB tvhftrace("main", linuxdvb_done); #endif +#if ENABLE_SATIP_CLIENT + tvhftrace("main", satip_done); +#endif #if ENABLE_TSFILE tvhftrace("main", tsfile_done); #endif diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c new file mode 100644 index 00000000..0f2b1578 --- /dev/null +++ b/src/input/mpegts/satip/satip.c @@ -0,0 +1,818 @@ +/* + * Tvheadend - SAT-IP client + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "input.h" +#include "htsbuf.h" +#include "htsmsg_xml.h" +#include "upnp.h" +#include "settings.h" +#include "satip_private.h" + +#include +#include + +/* + * 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, "", 7)) + return len; + /* Parse */ + xml = htsmsg_xml_deserialize(d->desc, errbuf, sizeof(errbuf)); + d->desc = NULL; + if (!xml) { + tvhlog(LOG_ERR, "satip_discovery_desc", "htsmsg_xml_deserialize error %s", errbuf); + goto finish; + } + if ((tags = htsmsg_get_map(xml, "tags")) == NULL) + goto finish; + if ((root = htsmsg_get_map(tags, "root")) == NULL) + goto finish; + if ((device = htsmsg_get_map(root, "tags")) == NULL) + goto finish; + if ((device = htsmsg_get_map(device, "device")) == NULL) + goto finish; + if ((device = htsmsg_get_map(device, "tags")) == NULL) + goto finish; + if ((cs = htsmsg_xml_get_cdata_str(device, "deviceType")) == NULL) + goto finish; + if (strcmp(cs, "urn:ses-com:device:SatIPServer:1")) + goto finish; + if ((friendlyname = htsmsg_xml_get_cdata_str(device, "friendlyName")) == NULL) + goto finish; + if ((manufacturer = htsmsg_xml_get_cdata_str(device, "manufacturer")) == NULL) + goto finish; + if ((manufacturerURL = htsmsg_xml_get_cdata_str(device, "manufacturerURL")) == NULL) + goto finish; + if ((modeldesc = htsmsg_xml_get_cdata_str(device, "modelDescription")) == NULL) + goto finish; + if ((modelname = htsmsg_xml_get_cdata_str(device, "modelName")) == NULL) + goto finish; + if ((modelnum = htsmsg_xml_get_cdata_str(device, "modelNumber")) == NULL) + goto finish; + if ((serialnum = htsmsg_xml_get_cdata_str(device, "serialNumber")) == NULL) + goto finish; + if ((presentation = htsmsg_xml_get_cdata_str(device, "presentationURL")) == NULL) + goto finish; + if ((tunercfg = htsmsg_xml_get_cdata_str(device, "urn:ses-com:satipX_SATIPCAP")) == NULL) + goto finish; + info.myaddr = strdup(d->myaddr); + info.addr = strdup(d->url.host); + info.uuid = strdup(d->uuid); + info.bootid = strdup(d->bootid); + info.configid = strdup(d->configid); + info.deviceid = strdup(d->deviceid); + info.location = strdup(d->location); + info.server = strdup(d->server); + info.friendlyname = strdup(friendlyname); + info.manufacturer = strdup(manufacturer); + info.manufacturerURL = strdup(manufacturerURL); + info.modeldesc = strdup(modeldesc); + info.modelname = strdup(modelname); + info.modelnum = strdup(modelnum); + info.serialnum = strdup(serialnum); + info.presentation = strdup(presentation); + info.tunercfg = strdup(tunercfg); + htsmsg_destroy(xml); + xml = NULL; + pthread_mutex_lock(&global_lock); + if (!satip_device_find(info.uuid)) + satip_device_create(&info); + pthread_mutex_unlock(&global_lock); + free(info.myaddr); + free(info.location); + free(info.server); + free(info.addr); + free(info.uuid); + free(info.bootid); + free(info.configid); + free(info.deviceid); + free(info.friendlyname); + free(info.manufacturer); + free(info.manufacturerURL); + free(info.modeldesc); + free(info.modelname); + free(info.modelnum); + free(info.serialnum); + free(info.presentation); + free(info.tunercfg); +finish: + htsmsg_destroy(xml); + return -EIO; +} + +static void +satip_discovery_http_fail(void *p) +{ + pthread_mutex_lock(&global_lock); + satip_discovery_destroy((satip_discovery_t *)p, 1); + pthread_mutex_unlock(&global_lock); +} + +static void +satip_discovery_timerq_cb(void *aux) +{ + satip_discovery_t *d, *next; + + lock_assert(&global_lock); + + next = TAILQ_FIRST(&satip_discoveries); + while (next) { + d = next; + next = TAILQ_NEXT(d, disc_link); + if (d->http_client) { + if (dispatch_clock - d->http_start > 4) + satip_discovery_destroy(d, 1);; + continue; + } + d->http_client = http_connect(&d->url, NULL, + satip_discovery_http_data, + satip_discovery_http_fail, + d); + if (d->http_client == NULL) + satip_discovery_destroy(d, 1); + d->http_start = dispatch_clock; + } + if (TAILQ_FIRST(&satip_discoveries)) + gtimer_arm(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 5); +} + +static void +satip_discovery_service_received + (uint8_t *data, size_t len, udp_connection_t *conn, + struct sockaddr_storage *storage) +{ + char *buf, *ptr, *saveptr; + char *argv[10]; + char *st = NULL; + char *location = NULL; + char *server = NULL; + char *uuid = NULL; + char *bootid = NULL; + char *configid = NULL; + char *deviceid = NULL; + char sockbuf[128]; + satip_discovery_t *d; + int n, i; + + if (len > 8191 || satip_discoveries_count > 100) + return; + buf = alloca(len+1); + memcpy(buf, data, len); + buf[len] = '\0'; + ptr = strtok_r(buf, "\r\n", &saveptr); + /* Request decoder */ + if (ptr) { + if (http_tokenize(ptr, argv, 3, -1) != 3) + return; + if (conn->multicast) { + if (strcmp(argv[0], "NOTIFY")) + return; + if (strcmp(argv[1], "*")) + return; + if (strcmp(argv[2], "HTTP/1.1")) + return; + } else { + if (strcmp(argv[0], "HTTP/1.1")) + return; + if (strcmp(argv[1], "200")) + return; + } + ptr = strtok_r(NULL, "\r\n", &saveptr); + } + /* Header decoder */ + while (1) { + if (ptr == NULL) + break; + if (http_tokenize(ptr, argv, 2, -1) == 2) { + if (strcmp(argv[0], "ST:") == 0) + st = argv[1]; + else if (strcmp(argv[0], "LOCATION:") == 0) + location = argv[1]; + else if (strcmp(argv[0], "SERVER:") == 0) + server = argv[1]; + else if (strcmp(argv[0], "BOOTID.UPNP.ORG:") == 0) + bootid = argv[1]; + else if (strcmp(argv[0], "CONFIGID.UPNP.ORG:") == 0) + configid = argv[1]; + else if (strcmp(argv[0], "DEVICEID.SES.COM:") == 0) + deviceid = argv[1]; + else if (strcmp(argv[0], "USN:") == 0) { + n = http_tokenize(argv[1], argv, 10, ':'); + for (i = 0; i < n+1; i++) + if (argv[i] && strcmp(argv[i], "uuid") == 0) { + uuid = argv[++i]; + break; + } + } + } + ptr = strtok_r(NULL, "\r\n", &saveptr); + } + /* Sanity checks */ + if (st == NULL || strcmp(st, "urn:ses-com:device:SatIPServer:1")) + return; + if (uuid == NULL && strlen(uuid) < 16) + return; + if (location == NULL && strncmp(location, "http://", 7)) + return; + if (bootid == NULL || configid == NULL || server == NULL) + return; + + /* Forward information to next layer */ + + d = calloc(1, sizeof(satip_discovery_t)); + if (inet_ntop(storage->ss_family, IP_IN_ADDR(conn->ip), + sockbuf, sizeof(sockbuf)) == NULL) { + satip_discovery_destroy(d, 0); + return; + } + d->myaddr = strdup(sockbuf); + d->location = strdup(location); + d->server = strdup(server); + d->uuid = strdup(uuid); + d->bootid = strdup(bootid); + d->configid = strdup(configid); + d->deviceid = strdup(deviceid ? deviceid : ""); + if (urlparse(d->location, &d->url)) { + satip_discovery_destroy(d, 0); + return; + } + + pthread_mutex_lock(&global_lock); + i = 1; + if (!satip_discovery_find(d) && !satip_device_find(d->uuid)) { + TAILQ_INSERT_TAIL(&satip_discoveries, d, disc_link); + satip_discoveries_count++; + gtimer_arm_ms(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 250); + i = 0; + } + pthread_mutex_unlock(&global_lock); + if (i) /* duplicate */ + satip_discovery_destroy(d, 0); +} + +static void +satip_discovery_service_destroy(upnp_service_t *us) +{ + satip_discovery_service = NULL; +} + +static void +satip_discovery_timer_cb(void *aux) +{ +#define MSG "\ +M-SEARCH * HTTP/1.1\r\n\ +HOST: 239.255.255.250:1900\r\n\ +MAN: \"ssdp:discover\"\r\n\ +MX: 2\r\n\ +ST: urn:ses-com:device:SatIPServer:1\r\n\ +\r\n" + htsbuf_queue_t q; + + if (!tvheadend_running) + return; + if (!upnp_running) { + gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1); + return; + } + if (satip_discovery_service == NULL) { + satip_discovery_service = upnp_service_create(upnp_service); + satip_discovery_service->us_received = satip_discovery_service_received; + satip_discovery_service->us_destroy = satip_discovery_service_destroy; + } + htsbuf_queue_init(&q, 0); + htsbuf_append(&q, MSG, sizeof(MSG)-1); + upnp_send(&q, NULL); + htsbuf_queue_flush(&q); + gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 3600); +#undef MSG +} + +/* + * Initialization + */ + +void satip_init ( void ) +{ + TAILQ_INIT(&satip_discoveries); + gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1); +} + +void satip_done ( void ) +{ + tvh_hardware_t *th, *n; + satip_discovery_t *d, *nd; + + pthread_mutex_lock(&global_lock); + for (th = LIST_FIRST(&tvh_hardware); th != NULL; th = n) { + n = LIST_NEXT(th, th_link); + if (idnode_is_instance(&th->th_id, &satip_device_class)) { + satip_device_destroy((satip_device_t *)th); + } + } + for (d = TAILQ_FIRST(&satip_discoveries); d != NULL; d = nd) { + nd = TAILQ_NEXT(d, disc_link); + satip_discovery_destroy(d, 1); + } + pthread_mutex_unlock(&global_lock); +} diff --git a/src/input/mpegts/satip/satip.h b/src/input/mpegts/satip/satip.h new file mode 100644 index 00000000..6e994c82 --- /dev/null +++ b/src/input/mpegts/satip/satip.h @@ -0,0 +1,26 @@ +/* + * Tvheadend - SAT-IP DVB private data + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_SATIP_H__ +#define __TVH_SATIP_H__ + +void satip_init( void ); +void satip_done( void ); + +#endif /* __TVH_SATIP_H__ */ diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c new file mode 100644 index 00000000..d6568e36 --- /dev/null +++ b/src/input/mpegts/satip/satip_frontend.c @@ -0,0 +1,1120 @@ +/* + * Tvheadend - SAT>IP DVB frontend + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _GNU_SOURCE +#include +#include +#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=.;src=;tuner=,,,,\ + * ,,,,,, + * ,;pids=,..., + * + * DVB-T: + * ver=1.1;tuner=,,,,,,,,\ + * ,,,,,;pids=,..., + */ + + /* level: + * Numerical value between 0 and 255 + * An incoming L-band satellite signal of + * -25dBm corresponds to 224 + * -65dBm corresponds to 32 + * No signal corresponds to 0 + * + * lock: + * lock Set to one of the following values: + * "0" the frontend is not locked + * "1" the frontend is locked + * + * quality: + * Numerical value between 0 and 15 + * Lowest value corresponds to highest error rate + * The value 15 shall correspond to + * -a BER lower than 2x10-4 after Viterbi for DVB-S + * -a PER lower than 10-7 for DVB-S2 + */ + while (len >= 12) { + if ((rtcp[0] & 0xc0) != 0x80) /* protocol version: v2 */ + return; + l = (((rtcp[2] << 8) | rtcp[3]) + 1) * 4; /* length of payload */ + if (l > len) + return; + if (rtcp[1] == 204 && l > 20 && /* packet type */ + rtcp[8] == 'S' && rtcp[9] == 'E' && + rtcp[10] == 'S' && rtcp[11] == '1') { + sl = (rtcp[14] << 8) | rtcp[15]; + if (sl > 0 && l - 16 >= sl) { + rtcp[sl + 16] = '\0'; + s = (char *)rtcp + 16; + tvhtrace("satip", "Status string: '%s'", s); + status = SIGNAL_NONE; + if (strncmp(s, "ver=1.0;", 8) == 0) { + if ((s = strstr(s + 8, ";tuner=")) == NULL) + return; + s += 7; + n = http_tokenize(s, argv, 4, ','); + if (n < 4) + return; + if (atoi(argv[0]) != lfe->sf_number) + return; + mmi->mmi_stats.signal = + (atoi(argv[1]) * 100) / lfe->sf_device->sd_sig_scale; + if (atoi(argv[2]) > 0) + status = SIGNAL_GOOD; + mmi->mmi_stats.snr = atoi(argv[3]); + goto ok; + } else if (strncmp(s, "ver=1.1;tuner=", 14) == 0) { + n = http_tokenize(s + 14, argv, 4, ','); + if (n < 4) + return; + if (atoi(argv[0]) != lfe->sf_number) + return; + mmi->mmi_stats.signal = + (atoi(argv[1]) * 100) / lfe->sf_device->sd_sig_scale; + if (atoi(argv[2]) > 0) + status = SIGNAL_GOOD; + mmi->mmi_stats.snr = atoi(argv[3]); + goto ok; + } + } + } + rtcp += l; + len -= l; + } + return; + +ok: + if (mmi->mmi_stats.snr < 2 && status == SIGNAL_GOOD) + status = SIGNAL_BAD; + else if (mmi->mmi_stats.snr < 4 && status == SIGNAL_GOOD) + status = SIGNAL_FAINT; + lfe->sf_status = status; +} + +static void +satip_frontend_default_tables + ( satip_frontend_t *lfe, mpegts_mux_t *mm ) +{ + psi_tables_default(mm); + psi_tables_dvb(mm); +} + +static void +satip_frontend_store_pids(char *buf, uint16_t *pids, int count) +{ + int first = 1; + char *s = buf; + + *s = '\0'; + while (count--) { + assert(*pids < 8192); + if (!first) + sprintf(s + strlen(s), ",%i", *(pids++)); + else { + sprintf(s + strlen(s), "%i", *(pids++)); + first = 0; + } + } +} + +static void +satip_frontend_pid_changed( satip_rtsp_connection_t *rtsp, + satip_frontend_t *lfe, const char *name ) +{ + char *add, *del; + int i, j, r, count, any = lfe->sf_pids_any; + int deleted; + + if (!lfe->sf_running) + return; + + pthread_mutex_lock(&lfe->sf_dvr_lock); + + if (lfe->sf_pids_count > lfe->sf_device->sd_pids_max) + any = lfe->sf_device->sd_fullmux_ok ? 1 : 0; + + if (any) { + + if (lfe->sf_pids_any_tuned) { + pthread_mutex_unlock(&lfe->sf_dvr_lock); + return; + } + lfe->sf_pids_any_tuned = 1; + memcpy(lfe->sf_pids_tuned, lfe->sf_pids, + lfe->sf_pids_count * sizeof(uint16_t)); + lfe->sf_pids_tcount = lfe->sf_pids_count; + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + r = satip_rtsp_play(rtsp, "all", NULL, NULL); + + } else if (!lfe->sf_device->sd_pids_deladd || + lfe->sf_pids_any_tuned || + lfe->sf_pids_tcount == 0) { + + lfe->sf_pids_any_tuned = 0; + count = lfe->sf_pids_count; + if (count > lfe->sf_device->sd_pids_max) + count = lfe->sf_device->sd_pids_max; + add = alloca(count * 5); + /* prioritize higher PIDs (tables are low prio) */ + satip_frontend_store_pids(add, + &lfe->sf_pids[lfe->sf_pids_count - count], + count); + memcpy(lfe->sf_pids_tuned, lfe->sf_pids, + lfe->sf_pids_count * sizeof(uint16_t)); + lfe->sf_pids_tcount = lfe->sf_pids_count; + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + r = satip_rtsp_play(rtsp, add, NULL, NULL); + + } else { + + add = alloca(lfe->sf_pids_count * 5); + del = alloca(lfe->sf_pids_count * 5); + add[0] = del[0] = '\0'; + +#if 0 + for (i = 0; i < lfe->sf_pids_count; i++) + printf("pid[%i] = %i\n", i, lfe->sf_pids[i]); + for (i = 0; i < lfe->sf_pids_tcount; i++) + printf("tuned[%i] = %i\n", i, lfe->sf_pids_tuned[i]); +#endif + + i = j = deleted = 0; + while (i < lfe->sf_pids_count && j < lfe->sf_pids_tcount) { + if (lfe->sf_pids[i] == lfe->sf_pids_tuned[j]) { + i++; j++; + } else if (lfe->sf_pids[i] < lfe->sf_pids_tuned[j]) { + i++; + } else { + sprintf(del + strlen(del), ",%i", lfe->sf_pids_tuned[j++]); + deleted++; + } + } + while (j < lfe->sf_pids_tcount) { + sprintf(del + strlen(del), ",%i", lfe->sf_pids_tuned[j++]); + deleted++; + } + + count = lfe->sf_pids_count + (lfe->sf_pids_tcount - deleted); + if (count > lfe->sf_device->sd_pids_max) + count = lfe->sf_device->sd_pids_max; + /* prioritize higher PIDs (tables are low prio) */ + /* count means "skip count" in following code */ + count = lfe->sf_pids_count - count; + + i = j = 0; + while (i < lfe->sf_pids_count && j < lfe->sf_pids_tcount) { + if (lfe->sf_pids[i] == lfe->sf_pids_tuned[j]) { + i++; j++; + } else if (lfe->sf_pids[i] < lfe->sf_pids_tuned[j]) { + if (count > 0) { + count--; + } else { + sprintf(add + strlen(add), ",%i", lfe->sf_pids[i]); + } + i++; + } else { + j++; + } + } + while (i < lfe->sf_pids_count) { + if (count > 0) + count--; + else + sprintf(add + strlen(add), ",%i", lfe->sf_pids[i++]); + } + + memcpy(lfe->sf_pids_tuned, lfe->sf_pids, + lfe->sf_pids_count * sizeof(uint16_t)); + lfe->sf_pids_tcount = lfe->sf_pids_count; + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + r = satip_rtsp_play(rtsp, NULL, add, del); + } + + if (r < 0) + tvherror("satip", "%s - failed to modify pids: %s", name, strerror(-r)); +} + +static void * +satip_frontend_input_thread ( void *aux ) +{ +#define PKTS 64 + satip_frontend_t *lfe = aux; + mpegts_mux_instance_t *mmi = lfe->sf_mmi; + satip_rtsp_connection_t *rtsp; + dvb_mux_t *lm; + char buf[256]; + uint8_t tsb[PKTS][1356 + 128]; + uint8_t rtcp[2048]; + uint8_t *p; + sbuf_t sb; + struct iovec iov[PKTS]; + struct mmsghdr msg[PKTS]; + int pos, nfds, i, r; + size_t c; + int tc; + tvhpoll_event_t ev[4]; + tvhpoll_t *efd; + int changing = 0, ms = -1, fatal = 0; + uint32_t seq = -1, nseq; + + lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf)); + + if (lfe->sf_rtp == NULL || lfe->sf_rtcp == NULL || mmi == NULL) + return NULL; + + lm = (dvb_mux_t *)mmi->mmi_mux; + + rtsp = satip_rtsp_connection(lfe->sf_device); + if (rtsp == NULL) + return NULL; + + /* Setup poll */ + efd = tvhpoll_create(4); + memset(ev, 0, sizeof(ev)); + ev[0].events = TVHPOLL_IN; + ev[0].fd = lfe->sf_rtp->fd; + ev[0].data.u64 = (uint64_t)lfe->sf_rtp; + ev[1].events = TVHPOLL_IN; + ev[1].fd = lfe->sf_rtcp->fd; + ev[1].data.u64 = (uint64_t)lfe->sf_rtcp; + ev[2].events = TVHPOLL_IN; + ev[2].fd = rtsp->fd; + ev[2].data.u64 = (uint64_t)rtsp; + ev[3].events = TVHPOLL_IN; + ev[3].fd = lfe->sf_dvr_pipe.rd; + ev[3].data.u64 = 0; + tvhpoll_add(efd, ev, 4); + + /* Read */ + memset(&msg, 0, sizeof(msg)); + for (i = 0; i < PKTS; i++) { + msg[i].msg_hdr.msg_iov = &iov[i]; + msg[i].msg_hdr.msg_iovlen = 1; + iov[i].iov_base = tsb[i]; + iov[i].iov_len = sizeof(tsb[0]); + } + + r = satip_rtsp_setup(rtsp, + lfe->sf_position, lfe->sf_number, + lfe->sf_rtp_port, &lm->lm_tuning, + 1); + if (r < 0) { + tvherror("satip", "%s - failed to tune", buf); + return NULL; + } + + sbuf_init_fixed(&sb, 18800); + + while (tvheadend_running && !fatal) { + + nfds = tvhpoll_wait(efd, ev, 1, ms); + + if (nfds > 0 && ev[0].data.u64 == 0) { + c = read(lfe->sf_dvr_pipe.rd, tsb[0], 1); + if (c == 1 && tsb[0][0] == 'c') { + ms = 20; + changing = 1; + continue; + } + tvhtrace("satip", "%s - input thread received shutdown", buf); + break; + } + + if (changing && rtsp->cmd == SATIP_RTSP_CMD_NONE) { + ms = -1; + changing = 0; + satip_frontend_pid_changed(rtsp, lfe, buf); + continue; + } + + if (nfds < 1) continue; + + if (ev[0].data.u64 == (uint64_t)rtsp) { + r = satip_rtsp_receive(rtsp); + if (r < 0) { + tvhlog(LOG_ERR, "satip", "%s - RTSP error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->cmd, rtsp->code); + fatal = 1; + } else if (r) { + switch (rtsp->cmd) { + case SATIP_RTSP_CMD_OPTIONS: + r = satip_rtsp_options_decode(rtsp); + if (r < 0) { + tvhlog(LOG_ERR, "satip", "%s - RTSP OPTIONS error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->cmd, rtsp->code); + fatal = 1; + } + break; + case SATIP_RTSP_CMD_SETUP: + r = satip_rtsp_setup_decode(rtsp); + if (r < 0 || rtsp->client_port != lfe->sf_rtp_port) { + tvhlog(LOG_ERR, "satip", "%s - RTSP SETUP error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->cmd, rtsp->code); + fatal = 1; + } else { + tvhdebug("satip", "%s #%i - new session %s stream id %li", + lfe->sf_device->sd_info.addr, lfe->sf_number, + rtsp->session, rtsp->stream_id); + pthread_mutex_lock(&global_lock); + satip_frontend_default_tables(lfe, mmi->mmi_mux); + pthread_mutex_unlock(&global_lock); + satip_frontend_pid_changed(rtsp, lfe, buf); + } + break; + default: + if (rtsp->code >= 400) { + tvhlog(LOG_ERR, "satip", "%s - RTSP cmd error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->cmd, rtsp->code); + fatal = 1; + } + break; + } + rtsp->cmd = SATIP_RTSP_CMD_NONE; + } + } + + if (rtsp->ping_time + rtsp->timeout / 2 < dispatch_clock && + rtsp->cmd == SATIP_RTSP_CMD_NONE) + satip_rtsp_options(rtsp); + + if (ev[0].data.u64 == (uint64_t)lfe->sf_rtcp) { + c = recv(lfe->sf_rtcp->fd, rtcp, sizeof(rtcp), MSG_DONTWAIT); + if (c > 0) + satip_frontend_decode_rtcp(lfe, buf, mmi, rtcp, c); + continue; + } + + if (ev[0].data.u64 != (uint64_t)lfe->sf_rtp) + continue; + + tc = recvmmsg(lfe->sf_rtp->fd, msg, PKTS, MSG_DONTWAIT, NULL); + + if (tc < 0) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) + continue; + if (errno == EOVERFLOW) { + tvhlog(LOG_WARNING, "satip", "%s - recvmsg() EOVERFLOW", buf); + continue; + } + tvhlog(LOG_ERR, "satip", "%s - recv() error %d (%s)", + buf, errno, strerror(errno)); + break; + } + + for (i = 0; i < tc; i++) { + p = tsb[i]; + c = msg[i].msg_len; + + /* Strip RTP header */ + if (c < 12) + continue; + if ((p[0] & 0xc0) != 0x80) + continue; + if ((p[1] & 0x7f) != 33) + continue; + pos = ((p[0] & 0x0f) * 4) + 12; + if (p[0] & 0x10) { + if (c < pos + 4) + continue; + pos += (((p[pos+2] << 8) | p[pos+3]) + 1) * 4; + } + if (c <= pos || ((c - pos) % 188) != 0) + continue; + /* Use uncorrectable value to notify RTP delivery issues */ + nseq = (p[2] << 8) | p[3]; + if (seq == -1) + seq = nseq; + else if (((seq + 1) & 0xffff) != nseq) + mmi->mmi_stats.unc++; + seq = nseq; + /* Process */ + sbuf_append(&sb, p + pos, c - pos); + mpegts_input_recv_packets((mpegts_input_t*)lfe, mmi, + &sb, 0, NULL, NULL); + } + } + + sbuf_free(&sb); + + ev[0].events = TVHPOLL_IN; + ev[0].fd = lfe->sf_rtp->fd; + ev[0].data.u64 = (uint64_t)lfe->sf_rtp; + ev[1].events = TVHPOLL_IN; + ev[1].fd = lfe->sf_rtcp->fd; + ev[1].data.u64 = (uint64_t)lfe->sf_rtcp; + ev[2].events = TVHPOLL_IN; + ev[2].fd = lfe->sf_dvr_pipe.rd; + ev[2].data.u64 = 0; + tvhpoll_rem(efd, ev, 3); + + if (rtsp->stream_id) { + r = satip_rtsp_teardown(rtsp); + if (r < 0) { + tvhtrace("satip", "%s - bad teardown", buf); + } else { + while (1) { + tvhpoll_wait(efd, ev, 1, -1); + r = satip_rtsp_receive(rtsp); + if (r) + break; + } + } + } + satip_rtsp_connection_close(rtsp); + + tvhpoll_destroy(efd); + return NULL; +#undef PKTS +} + +/* ************************************************************************** + * Tuning + * *************************************************************************/ + +static void +satip_frontend_signal_cb( void *aux ) +{ + satip_frontend_t *lfe = aux; + mpegts_mux_instance_t *mmi = LIST_FIRST(&lfe->mi_mux_active); + streaming_message_t sm; + signal_status_t sigstat; + service_t *svc; + + if (mmi == NULL) + return; + sigstat.status_text = signal2str(lfe->sf_status); + sigstat.snr = mmi->mmi_stats.snr; + sigstat.signal = mmi->mmi_stats.signal; + sigstat.ber = mmi->mmi_stats.ber; + sigstat.unc = mmi->mmi_stats.unc; + sm.sm_type = SMT_SIGNAL_STATUS; + sm.sm_data = &sigstat; + LIST_FOREACH(svc, &lfe->mi_transports, s_active_link) { + pthread_mutex_lock(&svc->s_stream_mutex); + streaming_pad_deliver(&svc->s_streaming_pad, &sm); + pthread_mutex_unlock(&svc->s_stream_mutex); + } + gtimer_arm_ms(&lfe->sf_monitor_timer, satip_frontend_signal_cb, lfe, 250); +} + +static int +satip_frontend_tune0 + ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi ) +{ + mpegts_mux_instance_t *cur = LIST_FIRST(&lfe->mi_mux_active); + udp_connection_t *uc1 = NULL, *uc2 = NULL; + int res = 0; + + if (cur != NULL) { + /* Already tuned */ + if (mmi == cur) + return 0; + + /* Stop current */ + cur->mmi_mux->mm_stop(cur->mmi_mux, 1); + } + assert(LIST_FIRST(&lfe->mi_mux_active) == NULL); + + assert(lfe->sf_pids == NULL); + assert(lfe->sf_pids_tuned == NULL); + lfe->sf_pids_count = 0; + lfe->sf_pids_tcount = 0; + lfe->sf_pids_size = 512; + lfe->sf_pids = calloc(lfe->sf_pids_size, sizeof(uint16_t)); + lfe->sf_pids_tuned = calloc(lfe->sf_pids_size, sizeof(uint16_t)); + lfe->sf_pids_any = 0; + lfe->sf_pids_any_tuned = 0; + lfe->sf_status = SIGNAL_NONE; + +retry: + if (lfe->sf_rtp == NULL) { + lfe->sf_rtp = udp_bind("satip", "satip_rtp", + lfe->sf_device->sd_info.myaddr, + lfe->sf_udp_rtp_port, + NULL, SATIP_BUF_SIZE); + if (lfe->sf_rtp == NULL || lfe->sf_rtp == UDP_FATAL_ERROR) + res = SM_CODE_TUNING_FAILED; + else + lfe->sf_rtp_port = ntohs(IP_PORT(lfe->sf_rtp->ip)); + } + if (lfe->sf_rtcp == NULL && !res) { + lfe->sf_rtcp = udp_bind("satip", "satip_rtcp", + lfe->sf_device->sd_info.myaddr, + lfe->sf_rtp_port + 1, + NULL, 16384); + if (lfe->sf_rtcp == NULL || lfe->sf_rtcp == UDP_FATAL_ERROR) { + if (lfe->sf_udp_rtp_port > 0) + res = SM_CODE_TUNING_FAILED; + else if (uc1 && uc2) + res = SM_CODE_TUNING_FAILED; + /* try to find another free UDP port */ + if (!res) { + if (uc1 == NULL) + uc1 = lfe->sf_rtp; + else + uc2 = lfe->sf_rtp; + lfe->sf_rtp = NULL; + goto retry; + } + } + } + udp_close(uc1); + udp_close(uc2); + + if (!res) { + lfe->sf_mmi = mmi; + + tvh_pipe(O_NONBLOCK, &lfe->sf_dvr_pipe); + tvhthread_create(&lfe->sf_dvr_thread, NULL, + satip_frontend_input_thread, lfe, 0); + + gtimer_arm_ms(&lfe->sf_monitor_timer, satip_frontend_signal_cb, lfe, 250); + + lfe->sf_running = 1; + } + + return res; +} + +static int +satip_frontend_tune1 + ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi ) +{ + char buf1[256], buf2[256]; + + lfe->mi_display_name((mpegts_input_t*)lfe, buf1, sizeof(buf1)); + mmi->mmi_mux->mm_display_name(mmi->mmi_mux, buf2, sizeof(buf2)); + tvhdebug("satip", "%s - starting %s", buf1, buf2); + + /* Tune */ + tvhtrace("satip", "%s - tuning", buf1); + return satip_frontend_tune0(lfe, mmi); +} + +/* ************************************************************************** + * Creation/Config + * *************************************************************************/ + +satip_frontend_t * +satip_frontend_create + ( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num ) +{ + const idclass_t *idc; + const char *uuid = NULL; + char id[12], lname[256]; + satip_frontend_t *lfe; + + /* Internal config ID */ + snprintf(id, sizeof(id), "%s #%d", dvb_type2str(type), num); + if (conf) + conf = htsmsg_get_map(conf, id); + if (conf) + uuid = htsmsg_get_str(conf, "uuid"); + + /* Class */ + if (type == DVB_TYPE_S) + idc = &satip_frontend_dvbs_class; + else if (type == DVB_TYPE_T) + idc = &satip_frontend_dvbt_class; + else { + tvherror("satip", "unknown FE type %d", type); + return NULL; + } + + // Note: there is a bit of a chicken/egg issue below, without the + // correct "fe_type" we cannot set the network (which is done + // in mpegts_input_create()). So we must set early. + lfe = calloc(1, sizeof(satip_frontend_t)); + lfe->sf_number = num; + lfe->sf_type = type; + lfe->sf_type_t2 = t2; + TAILQ_INIT(&lfe->sf_satconf); + pthread_mutex_init(&lfe->sf_dvr_lock, NULL); + lfe = (satip_frontend_t*)mpegts_input_create0((mpegts_input_t*)lfe, idc, uuid, conf); + if (!lfe) return NULL; + + /* Defaults */ + lfe->sf_position = -1; + + /* Callbacks */ + lfe->mi_is_free = satip_frontend_is_free; + lfe->mi_get_weight = satip_frontend_get_weight; + lfe->mi_get_priority = satip_frontend_get_priority; + lfe->mi_get_grace = satip_frontend_get_grace; + + /* Default name */ + if (!lfe->mi_name) { + snprintf(lname, sizeof(lname), "SAT>IP %s Tuner %s #%i", + dvb_type2str(type), sd->sd_info.addr, num); + lfe->mi_name = strdup(lname); + } + + /* Input callbacks */ + lfe->mi_is_enabled = satip_frontend_is_enabled; + lfe->mi_start_mux = satip_frontend_start_mux; + lfe->mi_stop_mux = satip_frontend_stop_mux; + lfe->mi_network_list = satip_frontend_network_list; + lfe->mi_open_pid = satip_frontend_open_pid; + lfe->mi_close_pid = satip_frontend_close_pid; + + /* Adapter link */ + lfe->sf_device = sd; + TAILQ_INSERT_TAIL(&sd->sd_frontends, lfe, sf_link); + + /* Create satconf */ + if (lfe->sf_type == DVB_TYPE_S) + satip_satconf_create(lfe, conf); + + return lfe; +} + +void +satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe ) +{ + char id[12]; + htsmsg_t *m = htsmsg_create_map(); + + /* Save frontend */ + mpegts_input_save((mpegts_input_t*)lfe, m); + htsmsg_add_str(m, "type", dvb_type2str(lfe->sf_type)); + satip_satconf_save(lfe, m); + if (lfe->ti_id.in_class == &satip_frontend_dvbs_class) + htsmsg_delete_field(m, "networks"); + + /* Add to list */ + snprintf(id, sizeof(id), "%s #%d", dvb_type2str(lfe->sf_type), lfe->sf_number); + htsmsg_add_msg(fe, id, m); +} + +void +satip_frontend_delete ( satip_frontend_t *lfe ) +{ + mpegts_mux_instance_t *mmi; + + lock_assert(&global_lock); + + /* Ensure we're stopped */ + if ((mmi = LIST_FIRST(&lfe->mi_mux_active))) + mmi->mmi_mux->mm_stop(mmi->mmi_mux, 1); + + gtimer_disarm(&lfe->sf_monitor_timer); + + /* Remove from adapter */ + TAILQ_REMOVE(&lfe->sf_device->sd_frontends, lfe, sf_link); + + /* Delete satconf */ + satip_satconf_destroy(lfe); + + /* Finish */ + mpegts_input_delete((mpegts_input_t*)lfe, 0); +} diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h new file mode 100644 index 00000000..a064b2f6 --- /dev/null +++ b/src/input/mpegts/satip/satip_private.h @@ -0,0 +1,279 @@ +/* + * Tvheadend - SAT-IP DVB private data + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_SATIP_PRIVATE_H__ +#define __TVH_SATIP_PRIVATE_H__ + +#include "input.h" +#include "htsbuf.h" +#include "udp.h" +#include "satip.h" + +#define SATIP_BUF_SIZE (4000*188) + +typedef struct satip_device_info satip_device_info_t; +typedef struct satip_device satip_device_t; +typedef struct satip_frontend satip_frontend_t; +typedef struct satip_satconf satip_satconf_t; + +struct satip_device_info +{ + char *myaddr; /* IP address of this host received data from the SAT>IP device */ + char *addr; /* IP address */ + char *uuid; + char *bootid; + char *configid; + char *deviceid; + char *location; /*< URL of the XML file */ + char *server; + char *friendlyname; + char *manufacturer; + char *manufacturerURL; + char *modeldesc; + char *modelname; + char *modelnum; + char *serialnum; + char *presentation; + char *tunercfg; /*< XML urn:ses-com:satipX_SATIPCAP contents */ +}; + +struct satip_device +{ + tvh_hardware_t; + + /* + * Adapter info + */ + satip_device_info_t sd_info; + + /* + * Frontends + */ + TAILQ_HEAD(,satip_frontend) sd_frontends; + + /* + * RTSP + */ + int sd_fullmux_ok; + int sd_pids_max; + int sd_pids_len; + int sd_pids_deladd; + int sd_sig_scale; + + int sd_rtsp_running; + pthread_t sd_rtsp_tid; + pthread_mutex_t sd_rtsp_lock; + pthread_cond_t sd_rtsp_cond; + TAILQ_HEAD(,satip_rtsp_request) sd_rtsp_queue; + time_t sd_rtsp_ping; + gtimer_t sd_rtsp_shutdown; +}; + +struct satip_frontend +{ + mpegts_input_t; + + /* + * Device + */ + satip_device_t *sf_device; + TAILQ_ENTRY(satip_frontend) sf_link; + + /* + * Frontend info + */ + int sf_number; + dvb_fe_type_t sf_type; + int sf_type_t2; + int sf_udp_rtp_port; + int sf_fullmux; + + /* + * Reception + */ + pthread_t sf_dvr_thread; + th_pipe_t sf_dvr_pipe; + pthread_mutex_t sf_dvr_lock; + pthread_cond_t sf_dvr_cond; + uint16_t *sf_pids; + uint16_t *sf_pids_tuned; + int sf_pids_any; + int sf_pids_any_tuned; + int sf_pids_size; + int sf_pids_count; + int sf_pids_tcount; /*< tuned count */ + int sf_running; + int sf_position; + udp_connection_t *sf_rtp; + udp_connection_t *sf_rtcp; + int sf_rtp_port; + mpegts_mux_instance_t *sf_mmi; + signal_state_t sf_status; + gtimer_t sf_monitor_timer; + + /* + * Configuration + */ + int sf_positions; + TAILQ_HEAD(,satip_satconf) sf_satconf; +}; + +struct satip_satconf +{ + + idnode_t sfc_id; + /* + * Parent + */ + satip_frontend_t *sfc_lfe; + TAILQ_ENTRY(satip_satconf) sfc_link; + + /* + * Config + */ + int sfc_enabled; + int sfc_position; + int sfc_priority; + char *sfc_name; + + /* + * Assigned networks to this SAT configuration + */ + idnode_set_t *sfc_networks; +}; + +/* + * Methods + */ + +void satip_device_init ( void ); + +void satip_device_done ( void ); + +void satip_device_save ( satip_device_t *sd ); + +void satip_device_destroy ( satip_device_t *sd ); + +satip_frontend_t * +satip_frontend_create + ( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num ); + +void satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *m ); + +void satip_frontend_delete ( satip_frontend_t *lfe ); + +/* + * SAT>IP Satconf configuration + */ +void satip_satconf_save ( satip_frontend_t *lfe, htsmsg_t *m ); + +void satip_satconf_destroy ( satip_frontend_t *lfe ); + +void satip_satconf_create + ( satip_frontend_t *lfe, htsmsg_t *conf ); + +void satip_satconf_updated_positions + ( satip_frontend_t *lfe ); + +int satip_satconf_get_priority + ( satip_frontend_t *lfe, mpegts_mux_t *mm ); + +int satip_satconf_get_position + ( satip_frontend_t *lfe, mpegts_mux_t *mm ); + +/* + * RTSP part + */ + +typedef enum { + SATIP_RTSP_CMD_NONE, + SATIP_RTSP_CMD_OPTIONS, + SATIP_RTSP_CMD_SETUP, + SATIP_RTSP_CMD_PLAY, + SATIP_RTSP_CMD_TEARDOWN, + SATIP_RTSP_CMD_DESCRIBE +} satip_rtsp_cmd_t; + +typedef struct satip_rtsp_connection { + /* decoded answer */ + int cseq; + int code; + char *header; + char *data; + /* state variables */ + int sending; + satip_rtsp_cmd_t cmd; + int port; + int client_port; + int timeout; + char *session; + uint64_t stream_id; + /* internal data */ + satip_device_t *device; + int fd; + char rbuf[4096]; + size_t rsize; + char *wbuf; + size_t wpos; + size_t wsize; + htsbuf_queue_t wq2; + satip_rtsp_cmd_t wq2_cmd; + int wq2_loaded; + time_t ping_time; +} satip_rtsp_connection_t; + +satip_rtsp_connection_t * +satip_rtsp_connection( satip_device_t *sd ); + +void +satip_rtsp_connection_close( satip_rtsp_connection_t *conn ); + +int +satip_rtsp_send_partial( satip_rtsp_connection_t *conn ); + +int +satip_rtsp_send( satip_rtsp_connection_t *conn, htsbuf_queue_t *q, + satip_rtsp_cmd_t cmd ); + +int +satip_rtsp_receive( satip_rtsp_connection_t *conn ); + +int +satip_rtsp_options_decode( satip_rtsp_connection_t *conn ); + +void +satip_rtsp_options( satip_rtsp_connection_t *conn ); + +int +satip_rtsp_setup_decode( satip_rtsp_connection_t *conn ); + +int +satip_rtsp_setup( satip_rtsp_connection_t *conn, + int src, int fe, int udp_port, + const dvb_mux_conf_t *dmc, + int connection_close ); + +int +satip_rtsp_play( satip_rtsp_connection_t *sd, const char *pids, + const char *addpids, const char *delpids ); + +int +satip_rtsp_teardown( satip_rtsp_connection_t *conn ); + +#endif /* __TVH_SATIP_PRIVATE_H__ */ diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c new file mode 100644 index 00000000..ab6574bc --- /dev/null +++ b/src/input/mpegts/satip/satip_rtsp.c @@ -0,0 +1,580 @@ +/* + * Tvheadend - SAT>IP DVB RTSP client + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "tvheadend.h" +#include "htsbuf.h" +#include "tcp.h" +#include "http.h" +#include "satip_private.h" + +/* + * + */ +satip_rtsp_connection_t * +satip_rtsp_connection( satip_device_t *sd ) +{ + satip_rtsp_connection_t *conn; + char errbuf[256]; + + conn = calloc(1, sizeof(satip_rtsp_connection_t)); + htsbuf_queue_init(&conn->wq2, 0); + conn->port = 554; + conn->timeout = 60; + conn->fd = tcp_connect(sd->sd_info.addr, conn->port, + errbuf, sizeof(errbuf), 2); + if (conn->fd < 0) { + tvhlog(LOG_ERR, "satip", "RTSP - unable to connect - %s", errbuf); + free(conn); + return NULL; + } + conn->device = sd; + conn->ping_time = dispatch_clock; + return conn; +} + +void +satip_rtsp_connection_close( satip_rtsp_connection_t *conn ) +{ + + htsbuf_queue_flush(&conn->wq2); + free(conn->session); + free(conn->header); + free(conn->data); + free(conn->wbuf); + if (conn->fd > 0) + close(conn->fd); + free(conn); +} + +int +satip_rtsp_send_partial( satip_rtsp_connection_t *conn ) +{ + ssize_t r; + + conn->sending = 1; + while (1) { + r = send(conn->fd, conn->wbuf + conn->wpos, conn->wsize - conn->wpos, MSG_DONTWAIT); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) + continue; + return -errno; + } + conn->wpos += r; + if (conn->wpos >= conn->wsize) { + conn->sending = 0; + return 1; + } + break; + } + return 0; +} + +int +satip_rtsp_send( satip_rtsp_connection_t *conn, htsbuf_queue_t *q, + satip_rtsp_cmd_t cmd ) +{ + int r; + + conn->ping_time = dispatch_clock; + conn->cmd = cmd; + free(conn->wbuf); + htsbuf_qprintf(q, "CSeq: %i\r\n\r\n", ++conn->cseq); + conn->wbuf = htsbuf_to_string(q); + conn->wsize = strlen(conn->wbuf); + conn->wpos = 0; +#if ENABLE_TRACE + tvhtrace("satip", "%s - sending RTSP cmd", conn->device->sd_info.addr); + tvhlog_hexdump("satip", conn->wbuf, conn->wsize); +#endif + while (!(r = satip_rtsp_send_partial(conn))) ; + return r; +} + +static int +satip_rtsp_send2( satip_rtsp_connection_t *conn, htsbuf_queue_t *q, + satip_rtsp_cmd_t cmd ) +{ + conn->wq2_loaded = 1; + conn->wq2_cmd = cmd; + htsbuf_appendq(&conn->wq2, q); + return 1; +} + +int +satip_rtsp_receive( satip_rtsp_connection_t *conn ) +{ + char buf[1024], *saveptr, *argv[3], *d, *p, *p1; + htsbuf_queue_t header, data; + int cseq_seen; + ssize_t r; + + r = recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT); + if (r == 0) + return -ESTRPIPE; + if (r < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) + return 0; + return -errno; + } +#if ENABLE_TRACE + if (r > 0) { + tvhtrace("satip", "%s - received RTSP answer", conn->device->sd_info.addr); + tvhlog_hexdump("satip", buf, r); + } +#endif + if (r + conn->rsize >= sizeof(conn->rbuf)) + return -EINVAL; + memcpy(conn->rbuf + conn->rsize, buf, r); + conn->rsize += r; + conn->rbuf[conn->rsize] = '\0'; + if (conn->rsize > 3 && + (d = strstr(conn->rbuf, "\r\n\r\n")) != NULL) { + *d = '\0'; + htsbuf_queue_init(&header, 0); + htsbuf_queue_init(&data, 0); + p = strtok_r(conn->rbuf, "\r\n", &saveptr); + if (p == NULL) + goto fail; + tvhtrace("satip", "%s - RTSP answer '%s'", conn->device->sd_info.addr, p); + if (http_tokenize(p, argv, 3, -1) != 3) + goto fail; + if (strcmp(argv[0], "RTSP/1.0")) + goto fail; + if ((conn->code = atoi(argv[1])) <= 0) + goto fail; + cseq_seen = 0; + while ((p = strtok_r(NULL, "\r\n", &saveptr)) != NULL) { + p1 = strdup(p); + if (http_tokenize(p, argv, 2, ':') != 2) + goto fail; + if (strcmp(argv[0], "CSeq") == 0) { + cseq_seen = conn->cseq == atoi(argv[1]); + } else { + htsbuf_append(&header, p1, strlen(p1)); + htsbuf_append(&header, "\n", 1); + } + free(p1); + } + if (!cseq_seen) + goto fail; + free(conn->header); + free(conn->data); + conn->header = htsbuf_to_string(&header); + conn->data = htsbuf_to_string(&data); +#if ENABLE_TRACE + tvhtrace("satip", "%s - received RTSP header", conn->device->sd_info.addr); + tvhlog_hexdump("satip", conn->header, strlen(conn->header)); + if (strlen(conn->data)) { + tvhtrace("satip", "%s - received RTSP data", conn->device->sd_info.addr); + tvhlog_hexdump("satip", conn->data, strlen(conn->data)); + } +#endif + htsbuf_queue_flush(&header); + htsbuf_queue_flush(&data); + conn->rsize = 0; + /* second write */ + if (conn->wq2_loaded && conn->code == 200) { + r = satip_rtsp_send(conn, &conn->wq2, conn->wq2_cmd); + htsbuf_queue_flush(&conn->wq2); + conn->wq2_loaded = 0; + return r; + } + return 1; +fail: + htsbuf_queue_flush(&header); + htsbuf_queue_flush(&data); + conn->rsize = 0; + return -EINVAL; + } + /* unfinished */ + return 0; +} + +/* + * + */ + +int +satip_rtsp_options_decode( satip_rtsp_connection_t *conn ) +{ + char *argv[32], *s, *saveptr; + int i, n, what = 0; + + s = strtok_r(conn->header, "\n", &saveptr); + while (s) { + n = http_tokenize(s, argv, 32, ','); + if (strcmp(argv[0], "Public:") == 0) + for (i = 1; i < n; i++) { + if (strcmp(argv[i], "DESCRIBE") == 0) + what |= 1; + else if (strcmp(argv[i], "SETUP") == 0) + what |= 2; + else if (strcmp(argv[i], "PLAY") == 0) + what |= 4; + else if (strcmp(argv[i], "TEARDOWN") == 0) + what |= 8; + } + s = strtok_r(NULL, "\n", &saveptr); + } + return (conn->code != 200 && what != 0x0f) ? -1 : 1; +} + +void +satip_rtsp_options( satip_rtsp_connection_t *conn ) +{ + htsbuf_queue_t q; + htsbuf_queue_init(&q, 0); + htsbuf_qprintf(&q, + "OPTIONS rtsp://%s/ RTSP/1.0\r\n", + conn->device->sd_info.addr); + satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_OPTIONS); + htsbuf_queue_flush(&q); +} + +int +satip_rtsp_setup_decode( satip_rtsp_connection_t *conn ) +{ + char *argv[32], *s, *saveptr; + int i, n; + + if (conn->code >= 400) + return -1; + if (conn->code != 200) + return 0; + conn->client_port = 0; + s = strtok_r(conn->header, "\n", &saveptr); + while (s) { + n = http_tokenize(s, argv, 32, ';'); + if (strcmp(argv[0], "Session:") == 0) { + conn->session = strdup(argv[1]); + for (i = 2; i < n; i++) { + if (strncmp(argv[i], "timeout=", 8) == 0) { + conn->timeout = atoi(argv[i] + 8); + if (conn->timeout <= 20 || conn->timeout > 3600) + return -1; + } + } + } else if (strcmp(argv[0], "com.ses.streamID:") == 0) { + conn->stream_id = atoll(argv[1]); + /* zero is valid stream id per specification */ + if (argv[1][0] == '0' && argv[1][0] == '\0') + conn->stream_id = 0; + else if (conn->stream_id <= 0) + return -1; + } else if (strcmp(argv[0], "Transport:") == 0) { + if (strcmp(argv[1], "RTP/AVP")) + return -1; + if (strcmp(argv[2], "unicast")) + return -1; + for (i = 2; i < n; i++) { + if (strncmp(argv[i], "client_port=", 12) == 0) + conn->client_port = atoi(argv[i] + 12); + } + } + s = strtok_r(NULL, "\n", &saveptr); + } + return 1; +} + +typedef struct tvh2satip { + int t; ///< TVH internal value + const char *s; ///< SATIP API value +} tvh2satip_t; + +#define TABLE_EOD -1 + +static const char * +satip_rtsp_setup_find(const char *prefix, tvh2satip_t *tbl, + int src, const char *defval) +{ + while (tbl->t >= 0) { + if (tbl->t == src) + return tbl->s; + tbl++; + } + tvhtrace("satip", "%s - cannot translate %d", prefix, src); + return defval; +} + +#define ADD(s, d, def) \ + strcat(buf, "&" #d "="), strcat(buf, satip_rtsp_setup_find(#d, d, dmc->s, def)) + +static void +satip_rtsp_add_val(const char *name, char *buf, uint32_t val) +{ + char sec[4]; + + sprintf(buf + strlen(buf), "&%s=%i", name, val / 1000); + if (val % 1000) { + sprintf(sec, ".%03i", val % 1000); + if (sec[3] == '0') { + sec[3] = '\0'; + if (sec[2] == '0') + sec[2] = '\0'; + } + } +} + +int +satip_rtsp_setup( satip_rtsp_connection_t *conn, int src, int fe, + int udp_port, const dvb_mux_conf_t *dmc, + int connection_close ) +{ + static tvh2satip_t msys[] = { + { .t = DVB_SYS_DVBT, "dvbt" }, + { .t = DVB_SYS_DVBT2, "dvbt2" }, + { .t = DVB_SYS_DVBS, "dvbs" }, + { .t = DVB_SYS_DVBS2, "dvbs2" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t pol[] = { + { .t = DVB_POLARISATION_HORIZONTAL, "h" }, + { .t = DVB_POLARISATION_VERTICAL, "v" }, + { .t = DVB_POLARISATION_CIRCULAR_LEFT, "l" }, + { .t = DVB_POLARISATION_CIRCULAR_RIGHT, "r" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t ro[] = { + { .t = DVB_ROLLOFF_AUTO, "0.35" }, + { .t = DVB_ROLLOFF_20, "0.20" }, + { .t = DVB_ROLLOFF_25, "0.25" }, + { .t = DVB_ROLLOFF_35, "0.35" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t mtype[] = { + { .t = DVB_MOD_AUTO, "auto" }, + { .t = DVB_MOD_QAM_16, "16qam" }, + { .t = DVB_MOD_QAM_32, "32qam" }, + { .t = DVB_MOD_QAM_64, "64qam" }, + { .t = DVB_MOD_QAM_128, "128qam"}, + { .t = DVB_MOD_QAM_256, "256qam"}, + { .t = DVB_MOD_QPSK, "qpsk" }, + { .t = DVB_MOD_PSK_8, "8psk" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t plts[] = { + { .t = DVB_PILOT_AUTO, "auto" }, + { .t = DVB_PILOT_ON, "on" }, + { .t = DVB_PILOT_OFF, "off" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t fec[] = { + { .t = DVB_FEC_AUTO, "auto" }, + { .t = DVB_FEC_1_2, "12" }, + { .t = DVB_FEC_2_3, "23" }, + { .t = DVB_FEC_3_4, "34" }, + { .t = DVB_FEC_3_5, "35" }, + { .t = DVB_FEC_4_5, "45" }, + { .t = DVB_FEC_5_6, "56" }, + { .t = DVB_FEC_7_8, "78" }, + { .t = DVB_FEC_8_9, "89" }, + { .t = DVB_FEC_9_10, "910" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t tmode[] = { + { .t = DVB_TRANSMISSION_MODE_AUTO, "auto" }, + { .t = DVB_TRANSMISSION_MODE_1K, "1k" }, + { .t = DVB_TRANSMISSION_MODE_2K, "2k" }, + { .t = DVB_TRANSMISSION_MODE_4K, "4k" }, + { .t = DVB_TRANSMISSION_MODE_8K, "8k" }, + { .t = DVB_TRANSMISSION_MODE_16K, "16k" }, + { .t = DVB_TRANSMISSION_MODE_32K, "32k" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t gi[] = { + { .t = DVB_GUARD_INTERVAL_AUTO, "auto" }, + { .t = DVB_GUARD_INTERVAL_1_4, "14" }, + { .t = DVB_GUARD_INTERVAL_1_8, "18" }, + { .t = DVB_GUARD_INTERVAL_1_16, "116" }, + { .t = DVB_GUARD_INTERVAL_1_32, "132" }, + { .t = DVB_GUARD_INTERVAL_1_128, "1128" }, + { .t = DVB_GUARD_INTERVAL_19_128, "19128" }, + { .t = DVB_GUARD_INTERVAL_19_256, "19256" }, + { .t = TABLE_EOD } + }; + + char buf[512]; + htsbuf_queue_t q; + int r; + + htsbuf_queue_init(&q, 0); + if (src > 0) + sprintf(buf, "src=%i&", src); + else + buf[0] = '\0'; + sprintf(buf + strlen(buf), "fe=%i", fe); + satip_rtsp_add_val("freq", buf, dmc->dmc_fe_freq); + if (dmc->dmc_fe_delsys == DVB_SYS_DVBS || + dmc->dmc_fe_delsys == DVB_SYS_DVBS2) { + satip_rtsp_add_val("sr", buf, dmc->u.dmc_fe_qpsk.symbol_rate); + ADD(dmc_fe_delsys, msys, "dvbs"); + ADD(dmc_fe_modulation, mtype, "qpsk"); + ADD(u.dmc_fe_qpsk.polarisation, pol, "h" ); + ADD(u.dmc_fe_qpsk.fec_inner, fec, "auto"); + ADD(dmc_fe_rolloff, ro, "0.35"); + if (dmc->dmc_fe_pilot != DVB_PILOT_AUTO) + ADD(dmc_fe_pilot, plts, "auto"); + } else { + if (dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_AUTO && + dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_NONE) + satip_rtsp_add_val("bw", buf, dmc->u.dmc_fe_ofdm.bandwidth); + ADD(dmc_fe_delsys, msys, "dvbt"); + if (dmc->dmc_fe_modulation != DVB_MOD_AUTO && + dmc->dmc_fe_modulation != DVB_MOD_NONE && + dmc->dmc_fe_modulation != DVB_MOD_QAM_AUTO) + ADD(dmc_fe_modulation, mtype, "64qam"); + if (dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_AUTO && + dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_NONE) + ADD(u.dmc_fe_ofdm.transmission_mode, tmode, "8k"); + if (dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_AUTO && + dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_NONE) + ADD(u.dmc_fe_ofdm.guard_interval, gi, "18"); + } + tvhtrace("satip", "setup params - %s", buf); + if (conn->stream_id > 0) + htsbuf_qprintf(&q, "SETUP rtsp://%s/stream=%li?", + conn->device->sd_info.addr, conn->stream_id); + else + htsbuf_qprintf(&q, "SETUP rtsp://%s/?", conn->device->sd_info.addr); + htsbuf_qprintf(&q, + "%s RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port=%i-%i\r\n", + buf, udp_port, udp_port+1); + if (conn->session) + htsbuf_qprintf(&q, "Session: %s\r\n", conn->session); + if (connection_close) + htsbuf_qprintf(&q, "Connection: close\r\n"); + r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_SETUP); + htsbuf_queue_flush(&q); + return r; +} + +static const char * +satip_rtsp_pids_strip( satip_rtsp_connection_t *conn, const char *s ) +{ + int maxlen = conn->device->sd_pids_len; + char *ptr; + + if (s == NULL) + return NULL; + while (*s == ',') + s++; + while (strlen(s) > maxlen) { + ptr = rindex(s, ','); + if (ptr == NULL) + abort(); + *ptr = '\0'; + } + if (*s == '\0') + return NULL; + return s; +} + +int +satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids, + const char *addpids, const char *delpids ) +{ + htsbuf_queue_t q; + int r, split = 0; + + pids = satip_rtsp_pids_strip(conn, pids); + addpids = satip_rtsp_pids_strip(conn, addpids); + delpids = satip_rtsp_pids_strip(conn, delpids); + + if (pids == NULL && addpids == NULL && delpids == NULL) + return 1; + + // printf("pids = '%s' addpids = '%s' delpids = '%s'\n", pids, addpids, delpids); + + htsbuf_queue_init(&q, 0); + htsbuf_qprintf(&q, "PLAY rtsp://%s/stream=%li?", + conn->device->sd_info.addr, conn->stream_id); + /* pids setup and add/del requests cannot be mixed per specification */ + if (pids) { + htsbuf_qprintf(&q, "pids=%s", pids); + } else { + if (delpids) + htsbuf_qprintf(&q, "delpids=%s", delpids); + if (addpids) { + if (delpids) { + /* try to maintain the maximum request size - simple split */ + if (strlen(addpids) + strlen(delpids) <= conn->device->sd_pids_len) + split = 1; + else + htsbuf_append(&q, "&", 1); + } + if (!split) + htsbuf_qprintf(&q, "addpids=%s", addpids); + } + } + htsbuf_qprintf(&q, " RTSP/1.0\r\nSession: %s\r\n", conn->session); + r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_PLAY); + htsbuf_queue_flush(&q); + if (r || !split) + return r; + + htsbuf_queue_init(&q, 0); + htsbuf_qprintf(&q, "PLAY rtsp://%s/stream=%li?", + conn->device->sd_info.addr, conn->stream_id); + htsbuf_qprintf(&q, "addpids=%s", addpids); + htsbuf_qprintf(&q, " RTSP/1.0\r\nSession: %s\r\n", conn->session); + r = satip_rtsp_send2(conn, &q, SATIP_RTSP_CMD_PLAY); + htsbuf_queue_flush(&q); + return r; +} + +int +satip_rtsp_teardown( satip_rtsp_connection_t *conn ) +{ + int r; + htsbuf_queue_t q; + htsbuf_queue_init(&q, 0); + htsbuf_qprintf(&q, + "TEARDOWN rtsp://%s/stream=%li RTSP/1.0\r\nSession: %s\r\n", + conn->device->sd_info.addr, conn->stream_id, conn->session); + r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_TEARDOWN); + htsbuf_queue_flush(&q); + return r; +} + +#if 0 +static int +satip_rtsp_describe_decode + ( satip_connection_t *conn ) +{ + if (header == NULL) + return 1; + printf("describe: %i\n", conn->code); + printf("header:\n%s\n", conn->header); + printf("data:\n%s\n", conn->data); + return 0; +} + +static void +satip_rtsp_describe( satip_connection_t *conn ) +{ + htsbuf_queue_t q; + htsbuf_queue_init(&q, 0); + htsbuf_qprintf(&q, + "DESCRIBE rtsp://%s/ RTSP/1.0\r\n", sd->sd_info.addr); + satip_rtsp_write(conn, &q); + htsbuf_queue_flush(&q); +} +#endif diff --git a/src/input/mpegts/satip/satip_satconf.c b/src/input/mpegts/satip/satip_satconf.c new file mode 100644 index 00000000..20b47c68 --- /dev/null +++ b/src/input/mpegts/satip/satip_satconf.c @@ -0,0 +1,322 @@ +/* + * Tvheadend - SAT>IP DVB satconf + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + *****************************************************************************/ diff --git a/src/main.c b/src/main.c index 5f356381..e8e2ff8d 100644 --- a/src/main.c +++ b/src/main.c @@ -134,6 +134,9 @@ const tvh_caps_t tvheadend_capabilities[] = { #if ENABLE_LINUXDVB { "linuxdvb", NULL }, #endif +#if ENABLE_SATIP_CLIENT + { "satip_client", NULL }, +#endif #if ENABLE_LIBAV { "transcoding", &transcoding_enabled }, #endif