From 91e5c9f7a8c01e89e623739caafdc2eebd243705 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 19:28:16 +0200
Subject: [PATCH 01/42] Reorganize the UDP code from IPTV to the shared
location
---
Makefile | 1 +
src/input/mpegts/iptv/iptv.c | 3 +-
src/input/mpegts/iptv/iptv_private.h | 2 +
src/input/mpegts/iptv/iptv_udp.c | 132 +---------
src/udp.c | 344 +++++++++++++++++++++++++++
src/udp.h | 69 ++++++
6 files changed, 427 insertions(+), 124 deletions(-)
create mode 100644 src/udp.c
create mode 100644 src/udp.h
diff --git a/Makefile b/Makefile
index 466d6405..ca7f8a55 100644
--- a/Makefile
+++ b/Makefile
@@ -76,6 +76,7 @@ SRCS = src/version.c \
src/access.c \
src/dtable.c \
src/tcp.c \
+ src/udp.c \
src/url.c \
src/http.c \
src/notify.c \
diff --git a/src/input/mpegts/iptv/iptv.c b/src/input/mpegts/iptv/iptv.c
index 4fd059c1..274cc893 100644
--- a/src/input/mpegts/iptv/iptv.c
+++ b/src/input/mpegts/iptv/iptv.c
@@ -239,7 +239,8 @@ iptv_input_stop_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi )
/* Close file */
if (im->mm_iptv_fd > 0) {
- close(im->mm_iptv_fd); // removes from poll
+ udp_close(im->mm_iptv_connection); // removes from poll
+ im->mm_iptv_connection = NULL;
im->mm_iptv_fd = -1;
}
diff --git a/src/input/mpegts/iptv/iptv_private.h b/src/input/mpegts/iptv/iptv_private.h
index edfabb5e..966a2746 100644
--- a/src/input/mpegts/iptv/iptv_private.h
+++ b/src/input/mpegts/iptv/iptv_private.h
@@ -23,6 +23,7 @@
#include "input.h"
#include "htsbuf.h"
#include "url.h"
+#include "udp.h"
#define IPTV_PKT_SIZE (300*188)
@@ -72,6 +73,7 @@ struct iptv_mux
mpegts_mux_t;
int mm_iptv_fd;
+ udp_connection_t *mm_iptv_connection;
char *mm_iptv_url;
char *mm_iptv_interface;
diff --git a/src/input/mpegts/iptv/iptv_udp.c b/src/input/mpegts/iptv/iptv_udp.c
index ea8cac0b..c001204f 100644
--- a/src/input/mpegts/iptv/iptv_udp.c
+++ b/src/input/mpegts/iptv/iptv_udp.c
@@ -45,137 +45,23 @@
static int
iptv_udp_start ( iptv_mux_t *im, const url_t *url )
{
- int fd, solip, rxsize, reuse = 1, ipv6 = 0;
- struct ifreq ifr;
- struct in_addr saddr;
- struct in6_addr s6addr;
- char name[256], buf[256];
+ char name[256];
+ udp_connection_t *conn;
im->mm_display_name((mpegts_mux_t*)im, name, sizeof(name));
- /* Determine if this is IPv6 */
- if (!inet_pton(AF_INET, url->host, &saddr)) {
- ipv6 = 1;
- if (!inet_pton(AF_INET6, url->host, &s6addr)) {
- tvherror("iptv", "%s - failed to process host", name);
- return SM_CODE_TUNING_FAILED;
- }
- }
-
- /* Open socket */
- if ((fd = tvh_socket(ipv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0)) == -1) {
- tvherror("iptv", "%s - failed to create socket [%s]",
- name, strerror(errno));
+ conn = udp_bind("iptv", name, url->host, url->port,
+ im->mm_iptv_interface, IPTV_PKT_SIZE);
+ if (conn == UDP_FATAL_ERROR)
return SM_CODE_TUNING_FAILED;
- }
-
- /* Mark reuse address */
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
-
- /* Bind to interface */
- memset(&ifr, 0, sizeof(ifr));
- if (im->mm_iptv_interface && *im->mm_iptv_interface) {
- snprintf(ifr.ifr_name, IFNAMSIZ, "%s", im->mm_iptv_interface);
- if (ioctl(fd, SIOCGIFINDEX, &ifr)) {
- tvherror("iptv", "%s - could not find interface %s",
- name, im->mm_iptv_interface);
- goto error;
- }
- }
-
- /* IPv4 */
- if (!ipv6) {
- struct ip_mreqn m;
- struct sockaddr_in sin;
- memset(&m, 0, sizeof(m));
- memset(&sin, 0, sizeof(sin));
-
- /* Bind */
- sin.sin_family = AF_INET;
- sin.sin_port = htons(url->port);
- sin.sin_addr = saddr;
- if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
- inet_ntop(AF_INET, &sin.sin_addr, buf, sizeof(buf));
- tvherror("iptv", "%s - cannot bind %s:%hd [e=%s]",
- name, buf, ntohs(sin.sin_port), strerror(errno));
- goto error;
- }
-
- /* Join group */
- m.imr_multiaddr = sin.sin_addr;
- m.imr_address.s_addr = 0;
-#if defined(PLATFORM_LINUX)
- m.imr_ifindex = ifr.ifr_ifindex;
-#elif defined(PLATFORM_FREEBSD)
- m.imr_ifindex = ifr.ifr_index;
-#endif
-#ifdef SOL_IP
- solip = SOL_IP;
-#else
- {
- struct protoent *pent;
- pent = getprotobyname("ip");
- solip = (pent != NULL) ? pent->p_proto : 0;
- }
-#endif
-
- if (setsockopt(fd, solip, IP_ADD_MEMBERSHIP, &m, sizeof(m))) {
- inet_ntop(AF_INET, &m.imr_multiaddr, buf, sizeof(buf));
- tvhwarn("iptv", "%s - cannot join %s [%s]",
- name, buf, strerror(errno));
- }
-
- /* Bind to IPv6 group */
- } else {
- struct ipv6_mreq m;
- struct sockaddr_in6 sin;
- memset(&m, 0, sizeof(m));
- memset(&sin, 0, sizeof(sin));
-
- /* Bind */
- sin.sin6_family = AF_INET6;
- sin.sin6_port = htons(url->port);
- sin.sin6_addr = s6addr;
- if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
- inet_ntop(AF_INET6, &sin.sin6_addr, buf, sizeof(buf));
- tvherror("iptv", "%s - cannot bind %s:%hd [e=%s]",
- name, buf, ntohs(sin.sin6_port), strerror(errno));
- goto error;
- }
-
- /* Join group */
- m.ipv6mr_multiaddr = sin.sin6_addr;
-#if defined(PLATFORM_LINUX)
- m.ipv6mr_interface = ifr.ifr_ifindex;
-#elif defined(PLATFORM_FREEBSD)
- m.ipv6mr_interface = ifr.ifr_index;
-#endif
-#ifdef SOL_IPV6
- if (setsockopt(fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &m, sizeof(m))) {
- inet_ntop(AF_INET, &m.ipv6mr_multiaddr, buf, sizeof(buf));
- tvhwarn("iptv", "%s - cannot join %s [%s]",
- name, buf, strerror(errno));
- }
-#else
- tvherror("iptv", "IPv6 multicast not supported");
- goto error;
-#endif
- }
-
- /* Increase RX buffer size */
- rxsize = IPTV_PKT_SIZE;
- if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rxsize, sizeof(rxsize)) == -1)
- tvhwarn("iptv", "%s - cannot increase UDP rx buffer size [%s]",
- name, strerror(errno));
+ if (conn == NULL)
+ return -1;
/* Done */
- im->mm_iptv_fd = fd;
+ im->mm_iptv_fd = conn->fd;
+ im->mm_iptv_connection = conn;
iptv_input_mux_started(im);
return 0;
-
-error:
- close(fd);
- return -1;
}
static ssize_t
diff --git a/src/udp.c b/src/udp.c
new file mode 100644
index 00000000..a52d84e1
--- /dev/null
+++ b/src/udp.c
@@ -0,0 +1,344 @@
+/*
+ * TVHeadend - UDP common routines
+ *
+ * Copyright (C) 2013 Adam Sutton
+ * 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 "udp.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if defined(PLATFORM_LINUX)
+#include
+#elif defined(PLATFORM_FREEBSD)
+# include
+# ifndef IPV6_ADD_MEMBERSHIP
+# define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
+# define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
+# endif
+#endif
+
+extern int tcp_preferred_address_family;
+
+static int
+udp_resolve( udp_connection_t *uc, int receiver )
+{
+ struct addrinfo hints, *res, *ressave, *use = NULL;
+ char port_buf[6];
+ int x;
+
+ snprintf(port_buf, 6, "%d", uc->port);
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = receiver ? AI_PASSIVE : 0;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ x = getaddrinfo(uc->host ? uc->host : "*", port_buf, &hints, &res);
+ if (x < 0) {
+ tvhlog(LOG_ERR, uc->subsystem, "getaddrinfo: %s: %s",
+ uc->host != NULL ? uc->host : "*",
+ x == EAI_SYSTEM ? strerror(errno) : gai_strerror(x));
+ return -1;
+ }
+
+ ressave = res;
+ while (res) {
+ if (res->ai_family == tcp_preferred_address_family) {
+ use = res;
+ break;
+ } else if (use == NULL) {
+ use = res;
+ }
+ res = res->ai_next;
+ }
+ if (use->ai_family == AF_INET6) {
+ uc->ip.ss_family = AF_INET6;
+ IP_AS_V6(uc->ip, port) = htons(uc->port);
+ memcpy(&IP_AS_V6(uc->ip, addr), &((struct sockaddr_in6 *)use->ai_addr)->sin6_addr,
+ sizeof(struct in6_addr));
+ uc->multicast = !!IN6_IS_ADDR_MULTICAST(&IP_AS_V6(uc->ip, addr));
+ } else if (use->ai_family == AF_INET) {
+ uc->ip.ss_family = AF_INET;
+ IP_AS_V4(uc->ip, port) = htons(uc->port);
+ IP_AS_V4(uc->ip, addr) = ((struct sockaddr_in *)use->ai_addr)->sin_addr;
+ uc->multicast = !!IN_MULTICAST(ntohl(IP_AS_V4(uc->ip, addr.s_addr)));
+ }
+ freeaddrinfo(ressave);
+ if (uc->ip.ss_family != AF_INET && uc->ip.ss_family != AF_INET6) {
+ tvherror(uc->subsystem, "%s - failed to process host '%s'", uc->name, uc->host);
+ return -1;
+ }
+ return 0;
+}
+
+udp_connection_t *
+udp_bind ( const char *subsystem, const char *name,
+ const char *bindaddr, int port,
+ const char *ifname, int rxsize )
+{
+ int fd, solip, reuse = 1;
+ struct ifreq ifr;
+ udp_connection_t *uc;
+ char buf[256];
+ socklen_t addrlen;
+
+ uc = calloc(1, sizeof(udp_connection_t));
+ uc->fd = -1;
+ uc->host = bindaddr ? strdup(bindaddr) : NULL;
+ uc->port = port;
+ uc->ifname = ifname ? strdup(ifname) : NULL;
+ uc->subsystem = subsystem ? strdup(subsystem) : NULL;
+ uc->name = name ? strdup(name) : NULL;
+ uc->rxtxsize = rxsize;
+
+ if (udp_resolve(uc, 1) < 0) {
+ udp_close(uc);
+ return UDP_FATAL_ERROR;
+ }
+
+ /* Open socket */
+ if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) {
+ tvherror(subsystem, "%s - failed to create socket [%s]",
+ name, strerror(errno));
+ udp_close(uc);
+ return UDP_FATAL_ERROR;
+ }
+
+ /* Mark reuse address */
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
+
+ /* Bind to interface */
+ memset(&ifr, 0, sizeof(ifr));
+ if (ifname && *ifname) {
+ snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
+ if (ioctl(fd, SIOCGIFINDEX, &ifr)) {
+ tvherror(subsystem, "%s - could not find interface %s",
+ name, ifname);
+ goto error;
+ }
+ }
+
+ /* IPv4 */
+ if (uc->ip.ss_family == AF_INET) {
+ struct ip_mreqn m;
+ memset(&m, 0, sizeof(m));
+
+ /* Bind */
+ if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in)) == -1) {
+ inet_ntop(AF_INET, &IP_AS_V4(uc->ip, addr), buf, sizeof(buf));
+ tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]",
+ name, buf, ntohs(IP_AS_V4(uc->ip, port)), strerror(errno));
+ goto error;
+ }
+
+ if (uc->multicast) {
+ /* Join group */
+ m.imr_multiaddr = IP_AS_V4(uc->ip, addr);
+ m.imr_address.s_addr = 0;
+#if defined(PLATFORM_LINUX)
+ m.imr_ifindex = ifr.ifr_ifindex;
+#elif defined(PLATFORM_FREEBSD)
+ m.imr_ifindex = ifr.ifr_index;
+#endif
+#ifdef SOL_IP
+ solip = SOL_IP;
+#else
+ {
+ struct protoent *pent;
+ pent = getprotobyname("ip");
+ solip = (pent != NULL) ? pent->p_proto : 0;
+ }
+#endif
+
+ if (setsockopt(fd, solip, IP_ADD_MEMBERSHIP, &m, sizeof(m))) {
+ inet_ntop(AF_INET, &m.imr_multiaddr, buf, sizeof(buf));
+ tvhwarn("iptv", "%s - cannot join %s [%s]",
+ name, buf, strerror(errno));
+ }
+ }
+
+ /* Bind to IPv6 group */
+ } else {
+ struct ipv6_mreq m;
+ memset(&m, 0, sizeof(m));
+
+ /* Bind */
+ if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in6)) == -1) {
+ inet_ntop(AF_INET6, &IP_AS_V6(uc->ip, addr), buf, sizeof(buf));
+ tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]",
+ name, buf, ntohs(IP_AS_V6(uc->ip, port)), strerror(errno));
+ goto error;
+ }
+
+ if (uc->multicast) {
+ /* Join group */
+ m.ipv6mr_multiaddr = IP_AS_V6(uc->ip, addr);
+#if defined(PLATFORM_LINUX)
+ m.ipv6mr_interface = ifr.ifr_ifindex;
+#elif defined(PLATFORM_FREEBSD)
+ m.ipv6mr_interface = ifr.ifr_index;
+#endif
+#ifdef SOL_IPV6
+ if (setsockopt(fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &m, sizeof(m))) {
+ inet_ntop(AF_INET, &m.ipv6mr_multiaddr, buf, sizeof(buf));
+ tvhwarn(subsystem, "%s - cannot join %s [%s]",
+ name, buf, strerror(errno));
+ }
+#else
+ tvherror("iptv", "IPv6 multicast not supported");
+ goto error;
+#endif
+ }
+ }
+
+ addrlen = sizeof(uc->ip);
+ getsockname(fd, (struct sockaddr *)&uc->ip, &addrlen);
+
+ /* Increase RX buffer size */
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rxsize, sizeof(rxsize)) == -1)
+ tvhwarn("iptv", "%s - cannot increase UDP rx buffer size [%s]",
+ name, strerror(errno));
+
+ uc->fd = fd;
+ return uc;
+
+error:
+ udp_close(uc);
+ return NULL;
+}
+
+udp_connection_t *
+udp_connect ( const char *subsystem, const char *name,
+ const char *host, int port,
+ const char *ifname, int txsize )
+{
+ int fd;
+ struct ifreq ifr;
+ udp_connection_t *uc;
+
+ uc = calloc(1, sizeof(udp_connection_t));
+ uc->fd = -1;
+ uc->host = host ? strdup(host) : NULL;
+ uc->port = port;
+ uc->ifname = ifname ? strdup(ifname) : NULL;
+ uc->subsystem = subsystem ? strdup(subsystem) : NULL;
+ uc->name = name ? strdup(name) : NULL;
+ uc->rxtxsize = txsize;
+
+ if (udp_resolve(uc, 1) < 0) {
+ udp_close(uc);
+ return UDP_FATAL_ERROR;
+ }
+
+ /* Open socket */
+ if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) {
+ tvherror(subsystem, "%s - failed to create socket [%s]",
+ name, strerror(errno));
+ udp_close(uc);
+ return UDP_FATAL_ERROR;
+ }
+
+ /* Bind to interface */
+ memset(&ifr, 0, sizeof(ifr));
+ if (ifname && *ifname) {
+ snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
+ if (ioctl(fd, SIOCGIFINDEX, &ifr)) {
+ tvherror(subsystem, "%s - could not find interface %s",
+ name, ifname);
+ goto error;
+ }
+ }
+
+ /* Increase TX buffer size */
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &txsize, sizeof(txsize)) == -1)
+ tvhwarn("iptv", "%s - cannot increase UDP tx buffer size [%s]",
+ name, strerror(errno));
+
+ uc->fd = fd;
+ return uc;
+
+error:
+ udp_close(uc);
+ return NULL;
+}
+
+void
+udp_close( udp_connection_t *uc )
+{
+ if (uc == NULL || uc == UDP_FATAL_ERROR)
+ return;
+ if (uc->fd >= 0)
+ close(uc->fd);
+ free(uc->host);
+ free(uc->ifname);
+ free(uc->subsystem);
+ free(uc->name);
+ free(uc);
+}
+
+int
+udp_write( udp_connection_t *uc, const void *buf, size_t len,
+ struct sockaddr_storage *storage )
+{
+ int r;
+
+ if (storage == NULL)
+ storage = &uc->ip;
+ while (len) {
+ r = sendto(uc->fd, buf, len, 0, (struct sockaddr*)storage,
+ storage->ss_family == AF_INET6 ?
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
+ usleep(100);
+ continue;
+ }
+ break;
+ }
+ len -= r;
+ buf += r;
+ }
+ return len;
+}
+
+int
+udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
+ struct sockaddr_storage *storage )
+{
+ htsbuf_data_t *hd;
+ int l, r = 0;
+ void *p;
+
+ while ((hd = TAILQ_FIRST(&q->hq_q)) != NULL) {
+ if (!r) {
+ l = hd->hd_data_len - hd->hd_data_off;
+ p = hd->hd_data + hd->hd_data_off;
+ r = udp_write(uc, p, l, storage);
+ }
+ htsbuf_data_free(q, hd);
+ }
+ q->hq_size = 0;
+ return r;
+}
diff --git a/src/udp.h b/src/udp.h
new file mode 100644
index 00000000..fe302e16
--- /dev/null
+++ b/src/udp.h
@@ -0,0 +1,69 @@
+/*
+ * tvheadend, UDP interface
+ * Copyright (C) 2013 Adam Sutton
+ * 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 UDP_H_
+#define UDP_H_
+
+#include
+#include "tcp.h"
+
+#define UDP_FATAL_ERROR ((void *)-1)
+
+#define IP_AS_V4(storage, f) ((struct sockaddr_in *)&(storage))->sin_##f
+#define IP_AS_V6(storage, f) ((struct sockaddr_in6 *)&(storage))->sin6_##f
+#define IP_IN_ADDR(storage) \
+ ((storage).ss_family == AF_INET6 ? \
+ &((struct sockaddr_in6 *)&(storage))->sin6_addr : \
+ (void *)&((struct sockaddr_in *)&(storage))->sin_addr)
+#define IP_PORT(storage) \
+ ((storage).ss_family == AF_INET6 ? \
+ ((struct sockaddr_in6 *)&(storage))->sin6_port : \
+ ((struct sockaddr_in *)&(storage))->sin_port)
+
+typedef struct udp_connection {
+ char *host;
+ int port;
+ int multicast;
+ char *ifname;
+ struct sockaddr_storage ip;
+ int fd;
+ char *subsystem;
+ char *name;
+ int rxtxsize;
+} udp_connection_t;
+
+udp_connection_t *
+udp_bind ( const char *subsystem, const char *name,
+ const char *bindaddr, int port,
+ const char *ifname, int rxsize );
+udp_connection_t *
+udp_connect ( const char *subsystem, const char *name,
+ const char *host, int port,
+ const char *ifname, int txsize );
+void
+udp_close ( udp_connection_t *uc );
+int
+udp_write( udp_connection_t *uc, const void *buf, size_t len,
+ struct sockaddr_storage *storage );
+int
+udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
+ struct sockaddr_storage *storage );
+
+
+#endif /* UDP_H_ */
From 1debbee964ad707f0411edf99f8e617f09d2ba0e Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 19:30:13 +0200
Subject: [PATCH 02/42] Add basic support for the UPnP protocol (client)
---
Makefile | 3 +
src/main.c | 7 ++
src/upnp.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/upnp.h | 47 ++++++++++++
4 files changed, 276 insertions(+)
create mode 100644 src/upnp.c
create mode 100644 src/upnp.h
diff --git a/Makefile b/Makefile
index ca7f8a55..bfd2da08 100644
--- a/Makefile
+++ b/Makefile
@@ -117,6 +117,9 @@ SRCS = src/version.c \
src/fsmonitor.c \
src/cron.c \
+SRCS-${CONFIG_UPNP} += \
+ src/upnp.c
+
SRCS += \
src/api.c \
src/api/api_status.c \
diff --git a/src/main.c b/src/main.c
index 6f818d18..2245ecde 100644
--- a/src/main.c
+++ b/src/main.c
@@ -41,6 +41,7 @@
#include "tcp.h"
#include "access.h"
#include "http.h"
+#include "upnp.h"
#include "webui/webui.h"
#include "epggrab.h"
#include "spawn.h"
@@ -767,6 +768,9 @@ main(int argc, char **argv)
tcp_server_init(opt_ipv6);
http_server_init(opt_bindaddr);
webui_init();
+#if ENABLE_UPNP
+ upnp_server_init(opt_bindaddr);
+#endif
service_mapper_init();
@@ -813,6 +817,9 @@ main(int argc, char **argv)
mainloop();
+#if ENABLE_UPNP
+ tvhftrace("main", upnp_server_done);
+#endif
tvhftrace("main", htsp_done);
tvhftrace("main", http_server_done);
tvhftrace("main", webui_done);
diff --git a/src/upnp.c b/src/upnp.c
new file mode 100644
index 00000000..d2ef9c35
--- /dev/null
+++ b/src/upnp.c
@@ -0,0 +1,219 @@
+/*
+ * tvheadend, UPnP interface
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "tvheadend.h"
+#include "tvhpoll.h"
+#include "upnp.h"
+
+int upnp_running;
+static pthread_t upnp_tid;
+pthread_mutex_t upnp_lock;
+
+TAILQ_HEAD(upnp_active_services, upnp_service);
+
+typedef struct upnp_data {
+ TAILQ_ENTRY(upnp_data) data_link;
+ struct sockaddr_storage storage;
+ htsbuf_queue_t queue;
+} upnp_data_t;
+
+TAILQ_HEAD(upnp_data_queue_write, upnp_data);
+
+static struct upnp_active_services upnp_services;
+static struct upnp_data_queue_write upnp_data_write;
+static struct sockaddr_storage upnp_ipv4_multicast;
+
+/*
+ *
+ */
+upnp_service_t *upnp_service_create0( upnp_service_t *us )
+{
+ pthread_mutex_lock(&upnp_lock);
+ TAILQ_INSERT_TAIL(&upnp_services, us, us_link);
+ pthread_mutex_unlock(&upnp_lock);
+ return us;
+}
+
+void upnp_service_destroy( upnp_service_t *us )
+{
+ pthread_mutex_lock(&upnp_lock);
+ TAILQ_REMOVE(&upnp_services, us, us_link);
+ us->us_destroy(us);
+ pthread_mutex_unlock(&upnp_lock);
+ free(us);
+}
+
+/*
+ *
+ */
+void
+upnp_send( htsbuf_queue_t *q, struct sockaddr_storage *storage )
+{
+ upnp_data_t *data;
+
+ if (!upnp_running)
+ return;
+ data = calloc(1, sizeof(upnp_data_t));
+ htsbuf_queue_init(&data->queue, 0);
+ htsbuf_appendq(&data->queue, q);
+ if (storage == NULL)
+ data->storage = upnp_ipv4_multicast;
+ else
+ data->storage = *storage;
+ pthread_mutex_lock(&upnp_lock);
+ TAILQ_INSERT_TAIL(&upnp_data_write, data, data_link);
+ pthread_mutex_unlock(&upnp_lock);
+}
+
+/*
+ * Discovery thread
+ */
+static void *
+upnp_thread( void *aux )
+{
+ char *bindaddr = aux;
+ tvhpoll_t *poll = tvhpoll_create(2);
+ tvhpoll_event_t ev[2];
+ upnp_data_t *data;
+ udp_connection_t *multicast = NULL, *unicast = NULL;
+ udp_connection_t *conn;
+ unsigned char buf[16384];
+ upnp_service_t *us;
+ struct sockaddr_storage ip;
+ socklen_t iplen;
+ size_t size;
+ int r;
+
+ multicast = udp_bind("upnp", "upnp_thread_multicast",
+ "239.255.255.250", 1900,
+ NULL, 32*1024);
+ if (multicast == NULL || multicast == UDP_FATAL_ERROR)
+ goto error;
+ unicast = udp_bind("upnp", "upnp_thread_unicast", bindaddr, 1900,
+ NULL, 32*1024);
+ if (unicast == NULL || unicast == UDP_FATAL_ERROR)
+ goto error;
+
+ memset(&ev, 0, sizeof(ev));
+ ev[0].fd = multicast->fd;
+ ev[0].events = TVHPOLL_IN;
+ ev[0].data.u64 = (uint64_t)multicast;
+ ev[1].fd = unicast->fd;
+ ev[1].events = TVHPOLL_IN;
+ ev[1].data.u64 = (uint64_t)unicast;
+ tvhpoll_add(poll, ev, 2);
+
+ while (upnp_running && multicast->fd >= 0) {
+ r = tvhpoll_wait(poll, ev, 2, 1000);
+
+ while (r-- > 0) {
+ if ((ev[r].events & TVHPOLL_IN) != 0) {
+ conn = (udp_connection_t *)ev[r].data.u64;
+ iplen = sizeof(ip);
+ size = recvfrom(conn->fd, buf, sizeof(buf), 0,
+ (struct sockaddr *)&ip, &iplen);
+#if ENABLE_TRACE
+ if (size > 0) {
+ char tbuf[256];
+ inet_ntop(ip.ss_family, IP_IN_ADDR(ip), tbuf, sizeof(tbuf));
+ tvhtrace("upnp", "%s - received data from %s:%hu [size=%li]",
+ conn == multicast ? "multicast" : "unicast",
+ tbuf, IP_PORT(ip), size);
+ tvhlog_hexdump("upnp", buf, size);
+ }
+#endif
+ /* TODO: a filter */
+ TAILQ_FOREACH(us, &upnp_services, us_link)
+ us->us_received(buf, size, conn, &ip);
+ }
+ }
+
+ while (1) {
+ pthread_mutex_lock(&upnp_lock);
+ data = TAILQ_FIRST(&upnp_data_write);
+ if (data)
+ TAILQ_REMOVE(&upnp_data_write, data, data_link);
+ pthread_mutex_unlock(&upnp_lock);
+ if (data == NULL)
+ break;
+ udp_write_queue(unicast, &data->queue, &data->storage);
+ htsbuf_queue_flush(&data->queue);
+ free(data);
+ }
+ }
+
+error:
+ upnp_running = 0;
+ tvhpoll_destroy(poll);
+ udp_close(unicast);
+ udp_close(multicast);
+ return NULL;
+}
+
+/*
+ * Fire up UPnP server
+ */
+void
+upnp_server_init(const char *bindaddr)
+{
+ int r;
+
+ memset(&upnp_ipv4_multicast, 0, sizeof(upnp_ipv4_multicast));
+ upnp_ipv4_multicast.ss_family = AF_INET;
+ IP_AS_V4(upnp_ipv4_multicast, port) = htons(1900);
+ r = inet_pton(AF_INET, "239.255.255.250", &IP_AS_V4(upnp_ipv4_multicast, addr));
+ assert(r);
+
+ pthread_mutex_init(&upnp_lock, NULL);
+ TAILQ_INIT(&upnp_data_write);
+ TAILQ_INIT(&upnp_services);
+ upnp_running = 1;
+ tvhthread_create(&upnp_tid, NULL, upnp_thread, (char *)bindaddr, 0);
+}
+
+void
+upnp_server_done(void)
+{
+ upnp_data_t *data;
+ upnp_service_t *us;
+
+ upnp_running = 0;
+ pthread_kill(upnp_tid, SIGTERM);
+ pthread_join(upnp_tid, NULL);
+ while ((us = TAILQ_FIRST(&upnp_services)) != NULL)
+ upnp_service_destroy(us);
+ while ((data = TAILQ_FIRST(&upnp_data_write)) != NULL) {
+ TAILQ_REMOVE(&upnp_data_write, data, data_link);
+ htsbuf_queue_flush(&data->queue);
+ free(data);
+ }
+}
diff --git a/src/upnp.h b/src/upnp.h
new file mode 100644
index 00000000..7753e430
--- /dev/null
+++ b/src/upnp.h
@@ -0,0 +1,47 @@
+/*
+ * tvheadend, UPnP interface
+ * 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 UPNP_H_
+#define UPNP_H_
+
+#include "http.h"
+#include "udp.h"
+
+extern int upnp_running;
+
+typedef struct upnp_service upnp_service_t;
+
+struct upnp_service {
+ TAILQ_ENTRY(upnp_service) us_link;
+ void (*us_received)(uint8_t *data, size_t len,
+ udp_connection_t *conn,
+ struct sockaddr_storage *storage);
+ void (*us_destroy)(upnp_service_t *us);
+};
+
+upnp_service_t *upnp_service_create0(upnp_service_t *us);
+#define upnp_service_create(us) \
+ upnp_service_create0(calloc(1, sizeof(struct us)))
+void upnp_service_destroy(upnp_service_t *service);
+
+void upnp_send(htsbuf_queue_t *q, struct sockaddr_storage *storage);
+
+void upnp_server_init(const char *bindaddr);
+void upnp_server_done(void);
+
+#endif /* UPNP_H_ */
From 1fb89b9487371bc3157d5508589b3e859f9ec734 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 19:35:21 +0200
Subject: [PATCH 03/42] http client fixes (curl based)
- check the private u64 value rather than fd (gdb problems)
- move the run call outside the connect routine (locking issues)
- try to fix more leaks (PR_Cleanup)
---
configure | 3 ++
src/http.h | 2 +
src/http/http_client.c | 85 ++++++++++++++++++++++++++++++++++--------
src/main.c | 7 +++-
4 files changed, 79 insertions(+), 18 deletions(-)
diff --git a/configure b/configure
index 3cd02ea3..00d302a7 100755
--- a/configure
+++ b/configure
@@ -165,6 +165,9 @@ fi
#
if enabled_or_auto curl; then
if check_pkg libcurl; then
+ if check_pkg nspr; then
+ enable nspr
+ fi
enable curl
elif enabled curl; then
die "Curl development support not foun (use --disable-curl)"
diff --git a/src/http.h b/src/http.h
index 120dda8f..eb3264ce 100644
--- a/src/http.h
+++ b/src/http.h
@@ -158,4 +158,6 @@ http_connect ( const url_t *url,
void *p );
void http_close ( http_client_t *hc );
+void curl_done ( void );
+
#endif /* HTTP_H_ */
diff --git a/src/http/http_client.c b/src/http/http_client.c
index 84b64c9c..3cba6459 100644
--- a/src/http/http_client.c
+++ b/src/http/http_client.c
@@ -31,6 +31,7 @@
#include
#include
#include
+#include
/*
@@ -41,6 +42,7 @@ struct http_client
CURL *hc_curl;
int hc_fd;
url_t hc_url;
+ int hc_init;
int hc_begin;
/* Callbacks */
@@ -55,10 +57,12 @@ struct http_client
/*
* Global state
*/
+static int http_running;
static tvhpoll_t *http_poll;
static TAILQ_HEAD(,http_client) http_clients;
static pthread_mutex_t http_lock;
static CURLM *http_curlm;
+static th_pipe_t http_pipe;
/*
* Disable
@@ -109,6 +113,7 @@ http_curl_socket ( CURL *c, int fd, int a, void *u, void *s )
if (a & CURL_POLL_OUT)
ev.events |= TVHPOLL_OUT;
ev.data.fd = fd;
+ ev.data.u64 = (uint64_t)hc;
hc->hc_fd = fd;
tvhpoll_add(http_poll, &ev, 1);
}
@@ -138,27 +143,41 @@ http_curl_data ( void *buf, size_t len, size_t n, void *p )
static void *
http_thread ( void *p )
{
- int n, e, run = 0;
+ int n, run = 0;
tvhpoll_event_t ev;
http_client_t *hc;
+ char c;
- while (tvheadend_running) {
+ while (http_running) {
n = tvhpoll_wait(http_poll, &ev, 1, -1);
if (n < 0) {
if (tvheadend_running)
tvherror("http_client", "tvhpoll_wait() error");
- break;
- } else {
+ } else if (n > 0) {
+ if ((uint64_t)&http_pipe == ev.data.u64) {
+ if (read(http_pipe.rd, &c, 1) == 1) {
+ if (c == 'n') {
+ pthread_mutex_lock(&http_lock);
+ TAILQ_FOREACH(hc, &http_clients, hc_link) {
+ if (hc->hc_init == 0)
+ continue;
+ hc->hc_init = 0;
+ curl_multi_socket_action(http_curlm, hc->hc_fd, 0, &run);
+ }
+ pthread_mutex_unlock(&http_lock);
+ } else {
+ /* end-of-task */
+ break;
+ }
+ }
+ continue;
+ }
pthread_mutex_lock(&http_lock);
TAILQ_FOREACH(hc, &http_clients, hc_link)
- if (hc->hc_fd == ev.data.fd)
+ if ((uint64_t)hc == ev.data.u64)
break;
- if (hc && (ev.events & (TVHPOLL_IN | TVHPOLL_OUT))) {
- e = 0;
- if (ev.events & TVHPOLL_IN) e |= CURL_POLL_IN;
- if (ev.events & TVHPOLL_OUT) e |= CURL_POLL_OUT;
- curl_multi_socket_action(http_curlm, ev.data.fd, 0, &run);
- }
+ if (hc && (ev.events & (TVHPOLL_IN | TVHPOLL_OUT)))
+ curl_multi_socket_action(http_curlm, hc->hc_fd, 0, &run);
pthread_mutex_unlock(&http_lock);
}
}
@@ -177,8 +196,6 @@ http_connect
http_client_fail_cb fail_cb,
void *p )
{
- int run;
-
/* Setup structure */
http_client_t *hc = calloc(1, sizeof(http_client_t));
hc->hc_curl = curl_easy_init();
@@ -187,6 +204,7 @@ http_connect
hc->hc_data = data_cb;
hc->hc_fail = fail_cb;
hc->hc_opaque = p;
+ hc->hc_init = 1;
/* Store */
pthread_mutex_lock(&http_lock);
@@ -198,9 +216,10 @@ http_connect
curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEFUNCTION, http_curl_data);
curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEDATA, hc);
curl_multi_add_handle(http_curlm, hc->hc_curl);
- curl_multi_socket_action(http_curlm, CURL_SOCKET_TIMEOUT, 0, &run);
pthread_mutex_unlock(&http_lock);
+ tvh_write(http_pipe.wr, "n", 1);
+
return hc;
}
@@ -224,6 +243,8 @@ pthread_t http_client_tid;
void
http_client_init ( void )
{
+ tvhpoll_event_t ev = { 0 };
+
/* Setup list */
pthread_mutex_init(&http_lock, NULL);
TAILQ_INIT(&http_clients);
@@ -233,21 +254,48 @@ http_client_init ( void )
http_curlm = curl_multi_init();
curl_multi_setopt(http_curlm, CURLMOPT_SOCKETFUNCTION, http_curl_socket);
+ /* Setup pipe */
+ tvh_pipe(O_NONBLOCK, &http_pipe);
+
/* Setup poll */
- http_poll = tvhpoll_create(10);
+ http_poll = tvhpoll_create(10);
+ ev.fd = http_pipe.rd;
+ ev.events = TVHPOLL_IN;
+ ev.data.u64 = (uint64_t)&http_pipe;
+ tvhpoll_add(http_poll, &ev, 1);
/* Setup thread */
+ http_running = 1;
tvhthread_create(&http_client_tid, NULL, http_thread, NULL, 0);
}
void
http_client_done ( void )
{
- pthread_kill(http_client_tid, SIGTERM);
+ http_running = 0;
+ tvh_write(http_pipe.wr, "", 1);
pthread_join(http_client_tid, NULL);
+ assert(TAILQ_FIRST(&http_clients) == NULL);
+ tvh_pipe_close(&http_pipe);
tvhpoll_destroy(http_poll);
curl_multi_cleanup(http_curlm);
+}
+
+
+void
+curl_done ( void )
+{
+#if ENABLE_NSPR
+ void PR_Cleanup( void );
+#endif
curl_global_cleanup();
+#if ENABLE_NSPR
+ /*
+ * Note: Curl depends on the NSPR library.
+ * The PR_Cleanup() call is mandatory to free NSPR resources.
+ */
+ PR_Cleanup();
+#endif
}
#else /* ENABLE_CURL */
@@ -262,4 +310,9 @@ http_client_done ( void )
{
}
+void
+curl_done ( void )
+{
+}
+
#endif /* ENABLE_CURL */
diff --git a/src/main.c b/src/main.c
index 2245ecde..534e99cc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -748,6 +748,8 @@ main(int argc, char **argv)
imagecache_init();
+ http_client_init();
+
service_init();
#if ENABLE_MPEGTS
@@ -764,7 +766,6 @@ main(int argc, char **argv)
timeshift_init();
#endif
- http_client_init();
tcp_server_init(opt_ipv6);
http_server_init(opt_bindaddr);
webui_init();
@@ -823,11 +824,11 @@ main(int argc, char **argv)
tvhftrace("main", htsp_done);
tvhftrace("main", http_server_done);
tvhftrace("main", webui_done);
- tvhftrace("main", http_client_done);
tvhftrace("main", fsmonitor_done);
#if ENABLE_MPEGTS
tvhftrace("main", mpegts_done);
#endif
+ tvhftrace("main", http_client_done);
// Note: the locking is obviously a bit redundant, but without
// we need to disable the gtimer_arm call in epg_save()
@@ -867,6 +868,8 @@ main(int argc, char **argv)
free(opt_tsfile.str);
+ curl_done();
+
return 0;
}
From 4830879ab275795993a8cb1ecba59c4d5c470478 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 19:47:24 +0200
Subject: [PATCH 04/42] URL parser fixes
---
src/main.c | 1 +
src/url.c | 24 +++++++++++++++++++-----
src/url.h | 1 +
3 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/src/main.c b/src/main.c
index 534e99cc..5f356381 100644
--- a/src/main.c
+++ b/src/main.c
@@ -859,6 +859,7 @@ main(int argc, char **argv)
tvhftrace("main", hts_settings_done);
tvhftrace("main", dvb_done);
tvhftrace("main", lang_str_done);
+ tvhftrace("main", urlparse_done);
tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend");
tvhlog_end();
diff --git a/src/url.c b/src/url.c
index 8398b2da..d4c3c20a 100644
--- a/src/url.c
+++ b/src/url.c
@@ -90,6 +90,11 @@ urlparse ( const char *str, url_t *url )
return 0;
}
+void
+urlparse_free( void )
+{
+}
+
/* Fallback to limited support */
#else /* ENABLE_URIPARSER */
@@ -100,25 +105,25 @@ urlparse ( const char *str, url_t *url )
#define HC "[a-z0-9\\-\\.]"
#define URL_RE "^([A-Za-z]+)://(("UC"+)(:("PC"+))?@)?("HC"+)(:([0-9]+))?(/[^\\?]*)?(.([^#]*))?(#(.*))?"
+static regex_t *urlparse_exp = NULL;
int
urlparse ( const char *str, url_t *url )
{
- static regex_t *exp = NULL;
regmatch_t m[16];
char buf[16];
/* Create regexp */
- if (!exp) {
- exp = calloc(1, sizeof(regex_t));
- if (regcomp(exp, URL_RE, REG_ICASE | REG_EXTENDED)) {
+ if (!urlparse_exp) {
+ urlparse_exp = calloc(1, sizeof(regex_t));
+ if (regcomp(urlparse_exp, URL_RE, REG_ICASE | REG_EXTENDED)) {
tvherror("url", "failed to compile regexp");
exit(1);
}
}
/* Execute */
- if (regexec(exp, str, ARRAY_SIZE(m), m, 0))
+ if (regexec(urlparse_exp, str, ARRAY_SIZE(m), m, 0))
return 1;
/* Extract data */
@@ -145,4 +150,13 @@ urlparse ( const char *str, url_t *url )
return 0;
}
+void
+urlparse_done( void )
+{
+ if (urlparse_exp) {
+ regfree(urlparse_exp);
+ free(urlparse_exp);
+ }
+}
+
#endif /* ENABLE_URIPARSER */
diff --git a/src/url.h b/src/url.h
index 200c5905..dd9aeb22 100644
--- a/src/url.h
+++ b/src/url.h
@@ -40,5 +40,6 @@ typedef struct url
} url_t;
int urlparse ( const char *str, url_t *url );
+void urlparse_done ( void );
#endif
From 8583936c715b10a86b8c5d1cfb9465fc9e0d83b9 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 19:52:23 +0200
Subject: [PATCH 05/42] 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
From 08bfdf883bcdd08149884d64dd60f9057aa7ea8c Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 19:54:02 +0200
Subject: [PATCH 06/42] tvhlog - do not queue messages when log is not running
---
src/tvhlog.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/tvhlog.c b/src/tvhlog.c
index 772fbd86..343e9909 100644
--- a/src/tvhlog.c
+++ b/src/tvhlog.c
@@ -270,7 +270,7 @@ void tvhlogv ( const char *file, int line,
pthread_mutex_lock(&tvhlog_mutex);
/* Check for full */
- if (tvhlog_queue_full) {
+ if (tvhlog_queue_full || !tvhlog_run) {
pthread_mutex_unlock(&tvhlog_mutex);
return;
}
From 76461dd5c1887c64d9b8c131479503d4df3e494d Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 18:21:07 +0200
Subject: [PATCH 07/42] Prevent SIGSEGV when bind to the TCP socket fails
---
src/tcp.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/tcp.c b/src/tcp.c
index 89cedf2a..272ba787 100644
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -640,6 +640,9 @@ tcp_server_delete(void *server)
tcp_server_t *ts = server;
tvhpoll_event_t ev;
+ if (server == NULL)
+ return;
+
memset(&ev, 0, sizeof(ev));
ev.fd = ts->serverfd;
ev.events = TVHPOLL_IN;
From 42b8b19d4e24ce0170591874fa907c94c8f567c4 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 18:21:51 +0200
Subject: [PATCH 08/42] Improve the SAT>IP RTSP implementation
---
src/input/mpegts/satip/satip_frontend.c | 41 +++++--
src/input/mpegts/satip/satip_private.h | 15 ++-
src/input/mpegts/satip/satip_rtsp.c | 143 +++++++++++++-----------
3 files changed, 128 insertions(+), 71 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index d6568e36..987d4ac1 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -637,7 +637,10 @@ satip_frontend_pid_changed( satip_rtsp_connection_t *rtsp,
lfe->sf_pids_tcount = lfe->sf_pids_count;
pthread_mutex_unlock(&lfe->sf_dvr_lock);
- r = satip_rtsp_play(rtsp, NULL, add, del);
+ if (add[0] != '\0' || del[0] != '\0')
+ r = satip_rtsp_play(rtsp, NULL, add, del);
+ else
+ r = 0;
}
if (r < 0)
@@ -663,6 +666,7 @@ satip_frontend_input_thread ( void *aux )
size_t c;
int tc;
tvhpoll_event_t ev[4];
+ tvhpoll_event_t evr;
tvhpoll_t *efd;
int changing = 0, ms = -1, fatal = 0;
uint32_t seq = -1, nseq;
@@ -690,6 +694,7 @@ satip_frontend_input_thread ( void *aux )
ev[2].events = TVHPOLL_IN;
ev[2].fd = rtsp->fd;
ev[2].data.u64 = (uint64_t)rtsp;
+ evr = ev[2];
ev[3].events = TVHPOLL_IN;
ev[3].fd = lfe->sf_dvr_pipe.rd;
ev[3].data.u64 = 0;
@@ -717,6 +722,18 @@ satip_frontend_input_thread ( void *aux )
while (tvheadend_running && !fatal) {
+ if (rtsp->sending) {
+ if ((evr.events & TVHPOLL_OUT) == 0) {
+ evr.events |= TVHPOLL_OUT;
+ tvhpoll_add(efd, &evr, 1);
+ }
+ } else {
+ if (evr.events & TVHPOLL_OUT) {
+ evr.events &= ~TVHPOLL_OUT;
+ tvhpoll_add(efd, &evr, 1);
+ }
+ }
+
nfds = tvhpoll_wait(efd, ev, 1, ms);
if (nfds > 0 && ev[0].data.u64 == 0) {
@@ -740,12 +757,12 @@ satip_frontend_input_thread ( void *aux )
if (nfds < 1) continue;
if (ev[0].data.u64 == (uint64_t)rtsp) {
- r = satip_rtsp_receive(rtsp);
+ r = satip_rtsp_run(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) {
+ } else if (r == SATIP_RTSP_READ_DONE) {
switch (rtsp->cmd) {
case SATIP_RTSP_CMD_OPTIONS:
r = satip_rtsp_options_decode(rtsp);
@@ -783,6 +800,7 @@ satip_frontend_input_thread ( void *aux )
}
}
+ /* We need to keep the session alive */
if (rtsp->ping_time + rtsp->timeout / 2 < dispatch_clock &&
rtsp->cmd == SATIP_RTSP_CMD_NONE)
satip_rtsp_options(rtsp);
@@ -862,11 +880,20 @@ satip_frontend_input_thread ( void *aux )
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)
+ if (r == SATIP_RTSP_INCOMPLETE) {
+ evr.events |= TVHPOLL_OUT;
+ tvhpoll_add(efd, &evr, 1);
+ }
+ r = 0;
+ while (r == SATIP_RTSP_INCOMPLETE) {
+ if (!rtsp->sending) {
+ evr.events &= ~TVHPOLL_OUT;
+ tvhpoll_add(efd, &evr, 1);
+ }
+ nfds = tvhpoll_wait(efd, ev, 1, -1);
+ if (nfds < 0)
break;
+ r = satip_rtsp_run(rtsp);
}
}
}
diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h
index a064b2f6..67554e5b 100644
--- a/src/input/mpegts/satip/satip_private.h
+++ b/src/input/mpegts/satip/satip_private.h
@@ -210,6 +210,11 @@ typedef enum {
SATIP_RTSP_CMD_DESCRIBE
} satip_rtsp_cmd_t;
+#define SATIP_RTSP_OK 1
+#define SATIP_RTSP_READ_DONE 1
+#define SATIP_RTSP_SEND_DONE 1
+#define SATIP_RTSP_INCOMPLETE 0
+
typedef struct satip_rtsp_connection {
/* decoded answer */
int cseq;
@@ -229,6 +234,8 @@ typedef struct satip_rtsp_connection {
int fd;
char rbuf[4096];
size_t rsize;
+ size_t hsize; /* header size */
+ size_t csize; /* contents size (exclude header) */
char *wbuf;
size_t wpos;
size_t wsize;
@@ -252,7 +259,7 @@ 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 );
+satip_rtsp_run( satip_rtsp_connection_t *conn );
int
satip_rtsp_options_decode( satip_rtsp_connection_t *conn );
@@ -276,4 +283,10 @@ satip_rtsp_play( satip_rtsp_connection_t *sd, const char *pids,
int
satip_rtsp_teardown( satip_rtsp_connection_t *conn );
+int
+satip_rtsp_describe_decode( satip_rtsp_connection_t *conn );
+
+int
+satip_rtsp_describe( 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
index ab6574bc..a9f2117a 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -74,25 +74,23 @@ satip_rtsp_send_partial( satip_rtsp_connection_t *conn )
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 SATIP_RTSP_INCOMPLETE;
return -errno;
}
conn->wpos += r;
if (conn->wpos >= conn->wsize) {
conn->sending = 0;
- return 1;
+ return SATIP_RTSP_SEND_DONE;
}
break;
}
- return 0;
+ return SATIP_RTSP_INCOMPLETE;
}
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);
@@ -104,8 +102,7 @@ satip_rtsp_send( satip_rtsp_connection_t *conn, htsbuf_queue_t *q,
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;
+ return satip_rtsp_send_partial(conn);
}
static int
@@ -115,23 +112,37 @@ satip_rtsp_send2( satip_rtsp_connection_t *conn, htsbuf_queue_t *q,
conn->wq2_loaded = 1;
conn->wq2_cmd = cmd;
htsbuf_appendq(&conn->wq2, q);
- return 1;
+ return SATIP_RTSP_SEND_DONE;
+}
+
+static char *
+satip_rtsp_hstrip(char *h)
+{
+ while (*h && *h <= ' ')
+ h++;
+ return h;
}
int
-satip_rtsp_receive( satip_rtsp_connection_t *conn )
+satip_rtsp_run( satip_rtsp_connection_t *conn )
{
char buf[1024], *saveptr, *argv[3], *d, *p, *p1;
- htsbuf_queue_t header, data;
+ htsbuf_queue_t header;
int cseq_seen;
ssize_t r;
+ size_t len;
+ if (conn->sending) {
+ r = satip_rtsp_send_partial(conn);
+ if (r < 0 || r == SATIP_RTSP_INCOMPLETE)
+ return 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 SATIP_RTSP_INCOMPLETE;
return -errno;
}
#if ENABLE_TRACE
@@ -145,11 +156,11 @@ satip_rtsp_receive( satip_rtsp_connection_t *conn )
memcpy(conn->rbuf + conn->rsize, buf, r);
conn->rsize += r;
conn->rbuf[conn->rsize] = '\0';
- if (conn->rsize > 3 &&
+ if (!conn->csize && conn->rsize > 3 &&
(d = strstr(conn->rbuf, "\r\n\r\n")) != NULL) {
+ conn->hsize = d - conn->rbuf + 4;
*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;
@@ -162,50 +173,60 @@ satip_rtsp_receive( satip_rtsp_connection_t *conn )
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]);
+ if (strncasecmp(p, "CSeq:", 5) == 0) {
+ p1 = satip_rtsp_hstrip(p + 5);
+ if (p1)
+ cseq_seen = conn->cseq == atoi(p1);
+ } else if (strncasecmp(p, "Content-Length:", 15) == 0) {
+ conn->csize = atoll(p + 15);
} else {
- htsbuf_append(&header, p1, strlen(p1));
+ htsbuf_append(&header, p, strlen(p));
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);
+ htsbuf_queue_flush(&header);
+ free(conn->data);
+ conn->data = NULL;
+ if (!conn->csize)
+ goto processed;
+ if (conn->rsize > conn->hsize)
+ goto data;
+ } else if (conn->hsize + conn->csize >= conn->rsize) {
+data:
+ conn->data = malloc(conn->csize + 1);
+ memcpy(conn->data, conn->rbuf + conn->hsize, conn->csize);
+ conn->data[conn->csize] = '\0';
+processed:
+ len = conn->hsize + conn->csize;
+ memcpy(conn->rbuf, conn->rbuf + len, conn->rsize - len);
+ conn->rsize -= len;
#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)) {
+ if (conn->csize) {
tvhtrace("satip", "%s - received RTSP data", conn->device->sd_info.addr);
- tvhlog_hexdump("satip", conn->data, strlen(conn->data));
+ tvhlog_hexdump("satip", conn->data, conn->csize);
}
#endif
- htsbuf_queue_flush(&header);
- htsbuf_queue_flush(&data);
- conn->rsize = 0;
+ conn->hsize = conn->csize = 0;
/* second write */
- if (conn->wq2_loaded && conn->code == 200) {
+ if (conn->wq2_loaded && conn->code == 200 && !conn->rsize) {
r = satip_rtsp_send(conn, &conn->wq2, conn->wq2_cmd);
htsbuf_queue_flush(&conn->wq2);
conn->wq2_loaded = 0;
return r;
}
- return 1;
+ return SATIP_RTSP_READ_DONE;
fail:
htsbuf_queue_flush(&header);
- htsbuf_queue_flush(&data);
conn->rsize = 0;
return -EINVAL;
}
- /* unfinished */
- return 0;
+ return SATIP_RTSP_INCOMPLETE;
}
/*
@@ -221,7 +242,7 @@ satip_rtsp_options_decode( satip_rtsp_connection_t *conn )
s = strtok_r(conn->header, "\n", &saveptr);
while (s) {
n = http_tokenize(s, argv, 32, ',');
- if (strcmp(argv[0], "Public:") == 0)
+ if (strcasecmp(argv[0], "Public:") == 0)
for (i = 1; i < n; i++) {
if (strcmp(argv[i], "DESCRIBE") == 0)
what |= 1;
@@ -234,7 +255,7 @@ satip_rtsp_options_decode( satip_rtsp_connection_t *conn )
}
s = strtok_r(NULL, "\n", &saveptr);
}
- return (conn->code != 200 && what != 0x0f) ? -1 : 1;
+ return (conn->code != 200 && what != 0x0f) ? -EIO : SATIP_RTSP_OK;
}
void
@@ -255,35 +276,33 @@ 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;
+ return -EIO;
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) {
+ if (strcasecmp(argv[0], "Session:") == 0) {
conn->session = strdup(argv[1]);
for (i = 2; i < n; i++) {
- if (strncmp(argv[i], "timeout=", 8) == 0) {
+ if (strncasecmp(argv[i], "timeout=", 8) == 0) {
conn->timeout = atoi(argv[i] + 8);
if (conn->timeout <= 20 || conn->timeout > 3600)
- return -1;
+ return -EIO;
}
}
- } else if (strcmp(argv[0], "com.ses.streamID:") == 0) {
+ } else if (strcasecmp(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;
+ return -EIO;
+ } else if (strcasecmp(argv[0], "Transport:") == 0) {
+ if (strcasecmp(argv[1], "RTP/AVP"))
+ return -EIO;
+ if (strcasecmp(argv[2], "unicast"))
+ return -EIO;
for (i = 2; i < n; i++) {
if (strncmp(argv[i], "client_port=", 12) == 0)
conn->client_port = atoi(argv[i] + 12);
@@ -291,7 +310,7 @@ satip_rtsp_setup_decode( satip_rtsp_connection_t *conn )
}
s = strtok_r(NULL, "\n", &saveptr);
}
- return 1;
+ return SATIP_RTSP_OK;
}
typedef struct tvh2satip {
@@ -499,9 +518,9 @@ satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids,
delpids = satip_rtsp_pids_strip(conn, delpids);
if (pids == NULL && addpids == NULL && delpids == NULL)
- return 1;
+ return -EINVAL;
- // printf("pids = '%s' addpids = '%s' delpids = '%s'\n", pids, addpids, delpids);
+ //printf("pids = '%s' addpids = '%s' delpids = '%s'\n", pids, addpids, delpids);
htsbuf_queue_init(&q, 0);
htsbuf_qprintf(&q, "PLAY rtsp://%s/stream=%li?",
@@ -527,7 +546,7 @@ satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids,
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)
+ if (r < 0 || !split)
return r;
htsbuf_queue_init(&q, 0);
@@ -554,27 +573,25 @@ satip_rtsp_teardown( satip_rtsp_connection_t *conn )
return r;
}
-#if 0
-static int
-satip_rtsp_describe_decode
- ( satip_connection_t *conn )
+int
+satip_rtsp_describe_decode( satip_rtsp_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;
+ return SATIP_RTSP_SEND_DONE;
}
-static void
-satip_rtsp_describe( satip_connection_t *conn )
+int
+satip_rtsp_describe( satip_rtsp_connection_t *conn )
{
+ int r;
+
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);
+ "DESCRIBE rtsp://%s/ RTSP/1.0\r\n", conn->device->sd_info.addr);
+ r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_DESCRIBE);
htsbuf_queue_flush(&q);
+ return r;
}
-#endif
From f150d5d7064a5004aa82359fe46f427c8db97c13 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 20:11:12 +0200
Subject: [PATCH 09/42] tvhpoll - use data.ptr instead data.u64 in
http_client.c and satip_frontend.c
---
src/http/http_client.c | 12 ++++++------
src/input/mpegts/satip/satip_frontend.c | 22 +++++++++++-----------
2 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/src/http/http_client.c b/src/http/http_client.c
index 3cba6459..38741f62 100644
--- a/src/http/http_client.c
+++ b/src/http/http_client.c
@@ -112,9 +112,9 @@ http_curl_socket ( CURL *c, int fd, int a, void *u, void *s )
ev.events |= TVHPOLL_IN;
if (a & CURL_POLL_OUT)
ev.events |= TVHPOLL_OUT;
- ev.data.fd = fd;
- ev.data.u64 = (uint64_t)hc;
- hc->hc_fd = fd;
+ ev.data.fd = fd;
+ ev.data.ptr = hc;
+ hc->hc_fd = fd;
tvhpoll_add(http_poll, &ev, 1);
}
@@ -154,7 +154,7 @@ http_thread ( void *p )
if (tvheadend_running)
tvherror("http_client", "tvhpoll_wait() error");
} else if (n > 0) {
- if ((uint64_t)&http_pipe == ev.data.u64) {
+ if (&http_pipe == ev.data.ptr) {
if (read(http_pipe.rd, &c, 1) == 1) {
if (c == 'n') {
pthread_mutex_lock(&http_lock);
@@ -174,7 +174,7 @@ http_thread ( void *p )
}
pthread_mutex_lock(&http_lock);
TAILQ_FOREACH(hc, &http_clients, hc_link)
- if ((uint64_t)hc == ev.data.u64)
+ if (hc == ev.data.ptr)
break;
if (hc && (ev.events & (TVHPOLL_IN | TVHPOLL_OUT)))
curl_multi_socket_action(http_curlm, hc->hc_fd, 0, &run);
@@ -261,7 +261,7 @@ http_client_init ( void )
http_poll = tvhpoll_create(10);
ev.fd = http_pipe.rd;
ev.events = TVHPOLL_IN;
- ev.data.u64 = (uint64_t)&http_pipe;
+ ev.data.ptr = &http_pipe;
tvhpoll_add(http_poll, &ev, 1);
/* Setup thread */
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 987d4ac1..7d24de86 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -687,17 +687,17 @@ satip_frontend_input_thread ( void *aux )
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[0].data.ptr = 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[1].data.ptr = lfe->sf_rtcp;
ev[2].events = TVHPOLL_IN;
ev[2].fd = rtsp->fd;
- ev[2].data.u64 = (uint64_t)rtsp;
+ ev[2].data.ptr = rtsp;
evr = ev[2];
ev[3].events = TVHPOLL_IN;
ev[3].fd = lfe->sf_dvr_pipe.rd;
- ev[3].data.u64 = 0;
+ ev[3].data.ptr = NULL;
tvhpoll_add(efd, ev, 4);
/* Read */
@@ -736,7 +736,7 @@ satip_frontend_input_thread ( void *aux )
nfds = tvhpoll_wait(efd, ev, 1, ms);
- if (nfds > 0 && ev[0].data.u64 == 0) {
+ if (nfds > 0 && ev[0].data.ptr == NULL) {
c = read(lfe->sf_dvr_pipe.rd, tsb[0], 1);
if (c == 1 && tsb[0][0] == 'c') {
ms = 20;
@@ -756,7 +756,7 @@ satip_frontend_input_thread ( void *aux )
if (nfds < 1) continue;
- if (ev[0].data.u64 == (uint64_t)rtsp) {
+ if (ev[0].data.ptr == rtsp) {
r = satip_rtsp_run(rtsp);
if (r < 0) {
tvhlog(LOG_ERR, "satip", "%s - RTSP error %d (%s) [%i-%i]",
@@ -805,14 +805,14 @@ satip_frontend_input_thread ( void *aux )
rtsp->cmd == SATIP_RTSP_CMD_NONE)
satip_rtsp_options(rtsp);
- if (ev[0].data.u64 == (uint64_t)lfe->sf_rtcp) {
+ if (ev[0].data.ptr == 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)
+ if (ev[0].data.ptr != lfe->sf_rtp)
continue;
tc = recvmmsg(lfe->sf_rtp->fd, msg, PKTS, MSG_DONTWAIT, NULL);
@@ -866,13 +866,13 @@ satip_frontend_input_thread ( void *aux )
ev[0].events = TVHPOLL_IN;
ev[0].fd = lfe->sf_rtp->fd;
- ev[0].data.u64 = (uint64_t)lfe->sf_rtp;
+ ev[0].data.ptr = 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[1].data.ptr = lfe->sf_rtcp;
ev[2].events = TVHPOLL_IN;
ev[2].fd = lfe->sf_dvr_pipe.rd;
- ev[2].data.u64 = 0;
+ ev[2].data.ptr = NULL;
tvhpoll_rem(efd, ev, 3);
if (rtsp->stream_id) {
From 9ca6f0c45d4a6bd05a5cfee7e3c02418d71c6e80 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 9 Apr 2014 20:30:44 +0200
Subject: [PATCH 10/42] Make URL parser more dynamic
---
src/http/http_client.c | 4 +-
src/input/mpegts/iptv/iptv.c | 1 +
src/input/mpegts/satip/satip.c | 6 ++-
src/url.c | 69 ++++++++++++++++++++++++++++------
src/url.h | 23 ++++++------
5 files changed, 76 insertions(+), 27 deletions(-)
diff --git a/src/http/http_client.c b/src/http/http_client.c
index 38741f62..5f7c56b2 100644
--- a/src/http/http_client.c
+++ b/src/http/http_client.c
@@ -81,6 +81,8 @@ http_remove ( http_client_t *hc )
/* Free CURL memory */
curl_easy_cleanup(hc->hc_curl);
hc->hc_curl = NULL;
+
+ urlreset(&hc->hc_url);
}
/*
@@ -199,7 +201,7 @@ http_connect
/* Setup structure */
http_client_t *hc = calloc(1, sizeof(http_client_t));
hc->hc_curl = curl_easy_init();
- hc->hc_url = *url;
+ urlcopy(&hc->hc_url, url);
hc->hc_conn = conn_cb;
hc->hc_data = data_cb;
hc->hc_fail = fail_cb;
diff --git a/src/input/mpegts/iptv/iptv.c b/src/input/mpegts/iptv/iptv.c
index 274cc893..f5ed6590 100644
--- a/src/input/mpegts/iptv/iptv.c
+++ b/src/input/mpegts/iptv/iptv.c
@@ -218,6 +218,7 @@ iptv_input_start_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi )
im->mm_active = NULL;
pthread_mutex_unlock(&iptv_lock);
+ urlreset(&url);
return ret;
}
diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c
index 0f2b1578..8dd439c3 100644
--- a/src/input/mpegts/satip/satip.c
+++ b/src/input/mpegts/satip/satip.c
@@ -466,6 +466,7 @@ satip_discovery_destroy(satip_discovery_t *d, int unlink)
}
if (d->http_client)
http_close(d->http_client);
+ urlreset(&d->url);
free(d->myaddr);
free(d->location);
free(d->server);
@@ -622,7 +623,7 @@ satip_discovery_timerq_cb(void *aux)
next = TAILQ_NEXT(d, disc_link);
if (d->http_client) {
if (dispatch_clock - d->http_start > 4)
- satip_discovery_destroy(d, 1);;
+ satip_discovery_destroy(d, 1);
continue;
}
d->http_client = http_connect(&d->url, NULL,
@@ -631,7 +632,8 @@ satip_discovery_timerq_cb(void *aux)
d);
if (d->http_client == NULL)
satip_discovery_destroy(d, 1);
- d->http_start = dispatch_clock;
+ else
+ d->http_start = dispatch_clock;
}
if (TAILQ_FIRST(&satip_discoveries))
gtimer_arm(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 5);
diff --git a/src/url.c b/src/url.c
index d4c3c20a..53ebdc4f 100644
--- a/src/url.c
+++ b/src/url.c
@@ -25,11 +25,40 @@
#include
#include
+
+void
+urlreset ( url_t *url )
+{
+ free(url->scheme);
+ free(url->user);
+ free(url->pass);
+ free(url->host);
+ free(url->path);
+ free(url->query);
+ free(url->frag);
+ free(url->raw);
+ memset(url, 0, sizeof(*url));
+}
+
+void
+urlcopy ( url_t *dst, const url_t *src )
+{
+ dst->scheme = strdup(src->scheme);
+ dst->user = strdup(src->user);
+ dst->pass = strdup(src->pass);
+ dst->host = strdup(src->host);
+ dst->port = src->port;
+ dst->path = strdup(src->path);
+ dst->query = strdup(src->query);
+ dst->frag = strdup(src->frag);
+ dst->raw = strdup(src->raw);
+}
+
/* Use liburiparser if available */
#if ENABLE_URIPARSER
#include
-int
+int
urlparse ( const char *str, url_t *url )
{
UriParserStateA state;
@@ -37,6 +66,11 @@ urlparse ( const char *str, url_t *url )
UriUriA uri;
char *s, buf[256];
+ if (str == NULL || url == NULL)
+ return -1;
+
+ urlreset(url);
+
/* Parse */
state.uri = &uri;
if (uriParseUriA(&state, str) != URI_SUCCESS) {
@@ -45,32 +79,36 @@ urlparse ( const char *str, url_t *url )
}
/* Store raw */
- strncpy(url->raw, str, sizeof(url->raw));
+ url->raw = strdup(str);
/* Copy */
#define uri_copy(y, x)\
+ if (x.first) {\
+ size_t len = x.afterLast - x.first;\
+ y = strndup(x.first, len);\
+ }
+#define uri_copy_static(y, x)\
if (x.first) {\
size_t len = x.afterLast - x.first;\
strncpy(y, x.first, len);\
- y[len] = 0;\
} else {\
- *y = 0;\
+ y[0] = '\0';\
}
uri_copy(url->scheme, uri.scheme);
uri_copy(url->host, uri.hostText);
uri_copy(url->user, uri.userInfo);
uri_copy(url->query, uri.query);
uri_copy(url->frag, uri.fragment);
- uri_copy(buf, uri.portText);
+ uri_copy_static(buf, uri.portText);
if (*buf)
url->port = atoi(buf);
else
url->port = 0;
- *url->path = 0;
path = uri.pathHead;
while (path) {
- strcat(url->path, "/");
uri_copy(buf, path->text);
+ url->path = realloc(url->path, strlen(url->path) + strlen(buf) + 2);
+ strcat(url->path, "/");
strcat(url->path, buf);
path = path->next;
}
@@ -81,8 +119,6 @@ urlparse ( const char *str, url_t *url )
if (s) {
strcpy(url->pass, s+1);
*s = 0;
- } else {
- *url->pass = 0;
}
/* Cleanup */
@@ -113,6 +149,11 @@ urlparse ( const char *str, url_t *url )
regmatch_t m[16];
char buf[16];
+ if (str == NULL || url == NULL)
+ return -1;
+
+ urlreset(url);
+
/* Create regexp */
if (!urlparse_exp) {
urlparse_exp = calloc(1, sizeof(regex_t));
@@ -124,10 +165,14 @@ urlparse ( const char *str, url_t *url )
/* Execute */
if (regexec(urlparse_exp, str, ARRAY_SIZE(m), m, 0))
- return 1;
+ return -1;
/* Extract data */
#define copy(x, i)\
+ {\
+ x = strndup(str+m[i].rm_so, m[i].rm_eo - m[i].rm_so);\
+ }(void)0
+#define copy_static(x, i)\
{\
int len = m[i].rm_eo - m[i].rm_so;\
if (len >= sizeof(x) - 1)\
@@ -140,12 +185,12 @@ urlparse ( const char *str, url_t *url )
copy(url->pass, 5);
copy(url->host, 6);
copy(url->path, 9);
- copy(buf, 8);
+ copy_static(buf, 8);
url->port = atoi(buf);
copy(url->query, 11);
copy(url->frag, 13);
- strncpy(url->raw, str, sizeof(url->raw));
+ url->raw = strdup(str);
return 0;
}
diff --git a/src/url.h b/src/url.h
index dd9aeb22..4d7da6f3 100644
--- a/src/url.h
+++ b/src/url.h
@@ -22,24 +22,23 @@
#include
-// TODO: limits are a bit arbitrary and it's a bit inflexible, but it
-// does keep things simple, not having dynamically allocated strings
-
/* URL structure */
typedef struct url
{
- char scheme[32];
- char user[128];
- char pass[128];
- char host[256];
- short port;
- char path[256];
- char query[1024];
- char frag[256];
- char raw[2048];
+ char *scheme;
+ char *user;
+ char *pass;
+ char *host;
+ short port;
+ char *path;
+ char *query;
+ char *frag;
+ char *raw;
} url_t;
+void urlreset ( url_t *url );
int urlparse ( const char *str, url_t *url );
void urlparse_done ( void );
+void urlcopy ( url_t *dst, const url_t *src );
#endif
From a3ad55f64ce3b9684a02e174245846ee55124b8a Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Mon, 14 Apr 2014 21:42:12 +0200
Subject: [PATCH 11/42] Add build-in http client
- supports SSL
- supports location redirections
- supports chunked data transfers
- supports HTTP/1.0 HTTP/1.1 and RTSP/1.0
---
Makefile | 2 +-
src/http.c | 32 +-
src/http.h | 225 +++-
src/http/http_client.c | 320 ------
src/httpc.c | 1645 +++++++++++++++++++++++++++
src/input/mpegts/iptv/iptv_http.c | 41 +-
src/input/mpegts/satip/satip.c | 65 +-
src/input/mpegts/satip/satip_rtsp.c | 5 +-
src/main.c | 26 +-
src/tcp.c | 4 +-
src/tvheadend.h | 5 +
src/utils.c | 35 +
support/httpc-test.txt | 81 ++
13 files changed, 2058 insertions(+), 428 deletions(-)
delete mode 100644 src/http/http_client.c
create mode 100644 src/httpc.c
create mode 100644 support/httpc-test.txt
diff --git a/Makefile b/Makefile
index e0bb0f6c..eaa63ad5 100644
--- a/Makefile
+++ b/Makefile
@@ -113,7 +113,7 @@ SRCS = src/version.c \
src/descrambler/descrambler.c \
src/service_mapper.c \
src/input.c \
- src/http/http_client.c \
+ src/httpc.c \
src/fsmonitor.c \
src/cron.c \
diff --git a/src/http.c b/src/http.c
index ddf70213..5932d8a9 100644
--- a/src/http.c
+++ b/src/http.c
@@ -62,6 +62,31 @@ static struct strtab HTTP_versiontab[] = {
static void http_parse_get_args(http_connection_t *hc, char *args);
+/**
+ *
+ */
+const char *
+http_cmd2str(int val)
+{
+ return val2str(val, HTTP_cmdtab);
+}
+
+int http_str2cmd(const char *str)
+{
+ return str2val(str, HTTP_cmdtab);
+}
+
+const char *
+http_ver2str(int val)
+{
+ return val2str(val, HTTP_versiontab);
+}
+
+int http_str2ver(const char *str)
+{
+ return str2val(str, HTTP_versiontab);
+}
+
/**
*
*/
@@ -547,7 +572,6 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill)
-
/*
* Delete all arguments associated with a connection
*/
@@ -582,7 +606,7 @@ http_arg_get(struct http_arg_list *list, const char *name)
* Set an argument associated with a connection
*/
void
-http_arg_set(struct http_arg_list *list, char *key, char *val)
+http_arg_set(struct http_arg_list *list, const char *key, const char *val)
{
http_arg_t *ra;
@@ -814,8 +838,8 @@ http_serve(int fd, void **opaque, struct sockaddr_storage *peer,
memset(&hc, 0, sizeof(http_connection_t));
*opaque = &hc;
- TAILQ_INIT(&hc.hc_args);
- TAILQ_INIT(&hc.hc_req_args);
+ http_arg_init(&hc.hc_args);
+ http_arg_init(&hc.hc_req_args);
hc.hc_fd = fd;
hc.hc_peer = peer;
diff --git a/src/http.h b/src/http.h
index eb3264ce..6abe0ea6 100644
--- a/src/http.h
+++ b/src/http.h
@@ -21,8 +21,9 @@
#include "htsbuf.h"
#include "url.h"
+#include "tvhpoll.h"
-TAILQ_HEAD(http_arg_list, http_arg);
+typedef TAILQ_HEAD(http_arg_list, http_arg) http_arg_list_t;
typedef RB_HEAD(,http_arg) http_arg_tree_t;
@@ -33,13 +34,76 @@ typedef struct http_arg {
char *val;
} http_arg_t;
-#define HTTP_STATUS_OK 200
+#define HTTP_STATUS_CONTINUE 100
+#define HTTP_STATUS_PSWITCH 101
+#define HTTP_STATUS_OK 200
+#define HTTP_STATUS_CREATED 201
+#define HTTP_STATUS_ACCEPTED 202
+#define HTTP_STATUS_NON_AUTH_INFO 203
+#define HTTP_STATUS_NO_CONTENT 204
+#define HTTP_STATUS_RESET_CONTENT 205
#define HTTP_STATUS_PARTIAL_CONTENT 206
-#define HTTP_STATUS_FOUND 302
-#define HTTP_STATUS_BAD_REQUEST 400
-#define HTTP_STATUS_UNAUTHORIZED 401
-#define HTTP_STATUS_NOT_FOUND 404
+#define HTTP_STATUS_MULTIPLE 300
+#define HTTP_STATUS_MOVED 301
+#define HTTP_STATUS_FOUND 302
+#define HTTP_STATUS_SEE_OTHER 303
+#define HTTP_STATUS_NOT_MODIFIED 304
+#define HTTP_STATUS_USE_PROXY 305
+#define HTTP_STATUS_TMP_REDIR 307
+#define HTTP_STATUS_BAD_REQUEST 400
+#define HTTP_STATUS_UNAUTHORIZED 401
+#define HTTP_STATUS_PAYMENT 402
+#define HTTP_STATUS_FORBIDDEN 403
+#define HTTP_STATUS_NOT_FOUND 404
+#define HTTP_STATUS_NOT_ALLOWED 405
+#define HTTP_STATUS_NOT_ACCEPTABLE 406
+#define HTTP_STATUS_PROXY_AUTH 407
+#define HTTP_STATUS_TIMEOUT 408
+#define HTTP_STATUS_CONFLICT 409
+#define HTTP_STATUS_GONE 410
+#define HTTP_STATUS_LENGTH 411
+#define HTTP_STATUS_PRECONDITION 412
+#define HTTP_STATUS_ENTITY_OVER 413
+#define HTTP_STATUS_URI_TOO_LONG 414
+#define HTTP_STATUS_UNSUPPORTED 415
+#define HTTP_STATUS_BAD_RANGE 417
+#define HTTP_STATUS_EXPECTATION 418
+#define HTTP_STATUS_INTERNAL 500
+#define HTTP_STATUS_NOT_IMPLEMENTED 501
+#define HTTP_STATUS_BAD_GATEWAY 502
+#define HTTP_STATUS_SERVICE 503
+#define HTTP_STATUS_GATEWAY_TIMEOUT 504
+#define HTTP_STATUS_HTTP_VERSION 505
+typedef enum http_state {
+ HTTP_CON_WAIT_REQUEST,
+ HTTP_CON_READ_HEADER,
+ HTTP_CON_END,
+ HTTP_CON_POST_DATA,
+ HTTP_CON_SENDING,
+ HTTP_CON_SENT,
+ HTTP_CON_RECEIVING,
+ HTTP_CON_DONE,
+ HTTP_CON_IDLE
+} http_state_t;
+
+typedef enum http_cmd {
+ HTTP_CMD_GET,
+ HTTP_CMD_HEAD,
+ HTTP_CMD_POST,
+ RTSP_CMD_DESCRIBE,
+ RTSP_CMD_OPTIONS,
+ RTSP_CMD_SETUP,
+ RTSP_CMD_TEARDOWN,
+ RTSP_CMD_PLAY,
+ RTSP_CMD_PAUSE,
+} http_cmd_t;
+
+typedef enum http_ver {
+ HTTP_VERSION_1_0,
+ HTTP_VERSION_1_1,
+ RTSP_VERSION_1_0,
+} http_ver_t;
typedef struct http_connection {
int hc_fd;
@@ -51,36 +115,15 @@ typedef struct http_connection {
char *hc_url_orig;
int hc_keep_alive;
- htsbuf_queue_t hc_reply;
+ htsbuf_queue_t hc_reply;
- struct http_arg_list hc_args;
+ http_arg_list_t hc_args;
- struct http_arg_list hc_req_args; /* Argumets from GET or POST request */
+ http_arg_list_t hc_req_args; /* Argumets from GET or POST request */
- enum {
- HTTP_CON_WAIT_REQUEST,
- HTTP_CON_READ_HEADER,
- HTTP_CON_END,
- HTTP_CON_POST_DATA,
- } hc_state;
-
- enum {
- HTTP_CMD_GET,
- HTTP_CMD_HEAD,
- HTTP_CMD_POST,
- RTSP_CMD_DESCRIBE,
- RTSP_CMD_OPTIONS,
- RTSP_CMD_SETUP,
- RTSP_CMD_TEARDOWN,
- RTSP_CMD_PLAY,
- RTSP_CMD_PAUSE,
- } hc_cmd;
-
- enum {
- HTTP_VERSION_1_0,
- HTTP_VERSION_1_1,
- RTSP_VERSION_1_0,
- } hc_version;
+ http_state_t hc_state;
+ http_cmd_t hc_cmd;
+ http_ver_t hc_version;
char *hc_username;
char *hc_password;
@@ -99,11 +142,21 @@ typedef struct http_connection {
} http_connection_t;
+const char *http_cmd2str(int val);
+int http_str2cmd(const char *str);
+const char *http_ver2str(int val);
+int http_str2ver(const char *str);
+
+static inline void http_arg_init(struct http_arg_list *list)
+{
+ TAILQ_INIT(list);
+}
+
void http_arg_flush(struct http_arg_list *list);
char *http_arg_get(struct http_arg_list *list, const char *name);
-void http_arg_set(struct http_arg_list *list, char *key, char *val);
+void http_arg_set(struct http_arg_list *list, const char *key, const char *val);
int http_tokenize(char *buf, char **vec, int vecsize, int delimiter);
@@ -142,22 +195,104 @@ int http_access_verify(http_connection_t *hc, int mask);
void http_deescape(char *s);
+/*
+ * HTTP/RTSP Client
+ */
+
typedef struct http_client http_client_t;
-typedef void (http_client_conn_cb) (void *p);
-typedef size_t (http_client_data_cb) (void *p, void *buf, size_t len);
-typedef void (http_client_fail_cb) (void *p);
+typedef struct http_client_wcmd {
+
+ TAILQ_ENTRY(http_client_wcmd) link;
+
+ enum http_cmd wcmd;
+ int wcseq;
+
+ void *wbuf;
+ size_t wpos;
+ size_t wsize;
+} http_client_wcmd_t;
+
+struct http_client {
+
+ TAILQ_ENTRY(http_client) hc_link;
+
+ int hc_fd;
+ char *hc_scheme;
+ char *hc_host;
+ int hc_port;
+ tvhpoll_t *hc_efd;
+ int hc_pevents;
+
+ int hc_code;
+ http_ver_t hc_version;
+ http_cmd_t hc_cmd;
+
+ struct http_arg_list hc_args; /* header */
+
+ void *hc_aux;
+ size_t hc_data_limit;
+ size_t hc_io_size;
+ char *hc_data; /* data body */
+ size_t hc_data_size; /* data body size - result for caller */
+
+ time_t hc_ping_time; /* last issued command */
+
+ char *hc_rbuf; /* read buffer */
+ size_t hc_rsize; /* read buffer size */
+ size_t hc_rpos; /* read buffer position */
+ size_t hc_hsize; /* header size in bytes */
+ size_t hc_csize; /* contents size in bytes */
+ char *hc_chunk;
+ size_t hc_chunk_size;
+ size_t hc_chunk_csize;
+ size_t hc_chunk_alloc;
+ size_t hc_chunk_pos;
+ char *hc_location;
+ int hc_redirects;
+ int hc_result;
+ int hc_shutdown:1;
+ int hc_sending:1;
+ int hc_reconnected:1;
+ int hc_keepalive:1;
+ int hc_in_data:1;
+ int hc_chunked:1;
+ int hc_chunk_trails:1;
+ int hc_handle_location:1; /* handle the redirection (location) requests */
+
+ http_client_wcmd_t *hc_wcmd;
+ TAILQ_HEAD(,http_client_wcmd) hc_wqueue;
+
+ int hc_cseq; /* RTSP */
+ int hc_rcseq; /* RTSP - expected cseq */
+
+ struct http_client_ssl *hc_ssl; /* ssl internals */
+
+ /* callbacks */
+ int (*hc_hdr_received) (http_client_t *hc);
+ int (*hc_data_received)(http_client_t *hc, void *buf, size_t len);
+ int (*hc_data_complete)(http_client_t *hc);
+ void (*hc_conn_closed) (http_client_t *hc, int err);
+};
void http_client_init ( void );
void http_client_done ( void );
-http_client_t*
-http_connect ( const url_t *url,
- http_client_conn_cb conn_cb,
- http_client_data_cb data_cb,
- http_client_fail_cb fail_cb,
- void *p );
-void http_close ( http_client_t *hc );
-void curl_done ( void );
+http_client_t*
+http_client_connect ( void *aux, http_ver_t ver,
+ const char *scheme, const char *host, int port );
+void http_client_register ( http_client_t *hc );
+void http_client_close ( http_client_t *hc );
+
+int
+http_client_send( http_client_t *hc, http_cmd_t cmd,
+ const char *path, const char *query,
+ http_arg_list_t *header, void *body, size_t body_size );
+int
+http_client_simple( http_client_t *hc, const url_t *url);
+int
+http_client_clear_state( http_client_t *hc );
+int
+http_client_run( http_client_t *hc );
#endif /* HTTP_H_ */
diff --git a/src/http/http_client.c b/src/http/http_client.c
deleted file mode 100644
index 5f7c56b2..00000000
--- a/src/http/http_client.c
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Tvheadend - HTTP client functions
- *
- * Copyright (C) 2013 Adam Sutton
- *
- * 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 "tvhpoll.h"
-#include "redblack.h"
-#include "queue.h"
-#include "url.h"
-#include "http.h"
-
-#if ENABLE_CURL
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-
-/*
- * Client definition
- */
-struct http_client
-{
- CURL *hc_curl;
- int hc_fd;
- url_t hc_url;
- int hc_init;
- int hc_begin;
-
- /* Callbacks */
- http_client_conn_cb *hc_conn;
- http_client_data_cb *hc_data;
- http_client_fail_cb *hc_fail;
- void *hc_opaque;
-
- TAILQ_ENTRY(http_client) hc_link;
-};
-
-/*
- * Global state
- */
-static int http_running;
-static tvhpoll_t *http_poll;
-static TAILQ_HEAD(,http_client) http_clients;
-static pthread_mutex_t http_lock;
-static CURLM *http_curlm;
-static th_pipe_t http_pipe;
-
-/*
- * Disable
- */
-static void
-http_remove ( http_client_t *hc )
-{
- tvhpoll_event_t ev;
- ev.fd = hc->hc_fd;
-
- /* Remove */
- curl_multi_remove_handle(http_curlm, hc->hc_curl);
- tvhpoll_rem(http_poll, &ev, 1);
- TAILQ_REMOVE(&http_clients, hc, hc_link);
-
- /* Free CURL memory */
- curl_easy_cleanup(hc->hc_curl);
- hc->hc_curl = NULL;
-
- urlreset(&hc->hc_url);
-}
-
-/*
- * New socket
- */
-static int
-http_curl_socket ( CURL *c, int fd, int a, void *u, void *s )
-{
- http_client_t *hc;
- tvhpoll_event_t ev = { 0 };
- ev.fd = fd;
-
- /* Find client */
- TAILQ_FOREACH(hc, &http_clients, hc_link)
- if (hc->hc_curl == c)
- break;
-
- /* Invalid */
- if (!hc)
- goto done;
-
- /* Remove */
- if (a == CURL_POLL_REMOVE) {
- //http_remove(hc);
-
- /* Set */
- } else if (a & CURL_POLL_INOUT) {
- if (a & CURL_POLL_IN)
- ev.events |= TVHPOLL_IN;
- if (a & CURL_POLL_OUT)
- ev.events |= TVHPOLL_OUT;
- ev.data.fd = fd;
- ev.data.ptr = hc;
- hc->hc_fd = fd;
- tvhpoll_add(http_poll, &ev, 1);
- }
-
- /* Done */
-done:
- return 0;
-}
-
-/*
- * Data
- */
-static size_t
-http_curl_data ( void *buf, size_t len, size_t n, void *p )
-{
- http_client_t *hc = p;
- if (!hc->hc_begin && hc->hc_conn)
- hc->hc_conn(hc->hc_opaque);
- hc->hc_begin = 1;
- len = hc->hc_data(hc->hc_opaque, buf, len * n);
- return len;
-}
-
-/*
- * Data thread
- */
-static void *
-http_thread ( void *p )
-{
- int n, run = 0;
- tvhpoll_event_t ev;
- http_client_t *hc;
- char c;
-
- while (http_running) {
- n = tvhpoll_wait(http_poll, &ev, 1, -1);
- if (n < 0) {
- if (tvheadend_running)
- tvherror("http_client", "tvhpoll_wait() error");
- } else if (n > 0) {
- if (&http_pipe == ev.data.ptr) {
- if (read(http_pipe.rd, &c, 1) == 1) {
- if (c == 'n') {
- pthread_mutex_lock(&http_lock);
- TAILQ_FOREACH(hc, &http_clients, hc_link) {
- if (hc->hc_init == 0)
- continue;
- hc->hc_init = 0;
- curl_multi_socket_action(http_curlm, hc->hc_fd, 0, &run);
- }
- pthread_mutex_unlock(&http_lock);
- } else {
- /* end-of-task */
- break;
- }
- }
- continue;
- }
- pthread_mutex_lock(&http_lock);
- TAILQ_FOREACH(hc, &http_clients, hc_link)
- if (hc == ev.data.ptr)
- break;
- if (hc && (ev.events & (TVHPOLL_IN | TVHPOLL_OUT)))
- curl_multi_socket_action(http_curlm, hc->hc_fd, 0, &run);
- pthread_mutex_unlock(&http_lock);
- }
- }
-
- return NULL;
-}
-
-/*
- * Setup a connection (async)
- */
-http_client_t *
-http_connect
- ( const url_t *url,
- http_client_conn_cb conn_cb,
- http_client_data_cb data_cb,
- http_client_fail_cb fail_cb,
- void *p )
-{
- /* Setup structure */
- http_client_t *hc = calloc(1, sizeof(http_client_t));
- hc->hc_curl = curl_easy_init();
- urlcopy(&hc->hc_url, url);
- hc->hc_conn = conn_cb;
- hc->hc_data = data_cb;
- hc->hc_fail = fail_cb;
- hc->hc_opaque = p;
- hc->hc_init = 1;
-
- /* Store */
- pthread_mutex_lock(&http_lock);
- TAILQ_INSERT_TAIL(&http_clients, hc, hc_link);
-
- /* Setup connection */
- curl_easy_setopt(hc->hc_curl, CURLOPT_URL, url->raw);
- curl_easy_setopt(hc->hc_curl, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEFUNCTION, http_curl_data);
- curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEDATA, hc);
- curl_multi_add_handle(http_curlm, hc->hc_curl);
- pthread_mutex_unlock(&http_lock);
-
- tvh_write(http_pipe.wr, "n", 1);
-
- return hc;
-}
-
-/*
- * Cancel
- */
-void
-http_close ( http_client_t *hc )
-{
- pthread_mutex_lock(&http_lock);
- http_remove(hc);
- free(hc);
- pthread_mutex_unlock(&http_lock);
-}
-
-/*
- * Initialise subsystem
- */
-pthread_t http_client_tid;
-
-void
-http_client_init ( void )
-{
- tvhpoll_event_t ev = { 0 };
-
- /* Setup list */
- pthread_mutex_init(&http_lock, NULL);
- TAILQ_INIT(&http_clients);
-
- /* Initialise curl */
- curl_global_init(CURL_GLOBAL_ALL);
- http_curlm = curl_multi_init();
- curl_multi_setopt(http_curlm, CURLMOPT_SOCKETFUNCTION, http_curl_socket);
-
- /* Setup pipe */
- tvh_pipe(O_NONBLOCK, &http_pipe);
-
- /* Setup poll */
- http_poll = tvhpoll_create(10);
- ev.fd = http_pipe.rd;
- ev.events = TVHPOLL_IN;
- ev.data.ptr = &http_pipe;
- tvhpoll_add(http_poll, &ev, 1);
-
- /* Setup thread */
- http_running = 1;
- tvhthread_create(&http_client_tid, NULL, http_thread, NULL, 0);
-}
-
-void
-http_client_done ( void )
-{
- http_running = 0;
- tvh_write(http_pipe.wr, "", 1);
- pthread_join(http_client_tid, NULL);
- assert(TAILQ_FIRST(&http_clients) == NULL);
- tvh_pipe_close(&http_pipe);
- tvhpoll_destroy(http_poll);
- curl_multi_cleanup(http_curlm);
-}
-
-
-void
-curl_done ( void )
-{
-#if ENABLE_NSPR
- void PR_Cleanup( void );
-#endif
- curl_global_cleanup();
-#if ENABLE_NSPR
- /*
- * Note: Curl depends on the NSPR library.
- * The PR_Cleanup() call is mandatory to free NSPR resources.
- */
- PR_Cleanup();
-#endif
-}
-
-#else /* ENABLE_CURL */
-
-void
-http_client_init ( void )
-{
-}
-
-void
-http_client_done ( void )
-{
-}
-
-void
-curl_done ( void )
-{
-}
-
-#endif /* ENABLE_CURL */
diff --git a/src/httpc.c b/src/httpc.c
new file mode 100644
index 00000000..5e3a523c
--- /dev/null
+++ b/src/httpc.c
@@ -0,0 +1,1645 @@
+/*
+ * Tvheadend - HTTP client functions
+ *
+ * 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 "http.h"
+#include "tcp.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#define HTTPCLIENT_TESTSUITE 1
+
+struct http_client_ssl {
+ int connected;
+ int shutdown;
+ int notified;
+
+ SSL_CTX *ctx;
+ SSL *ssl;
+
+ BIO *rbio;
+ char *rbio_buf;
+ size_t rbio_size;
+ size_t rbio_pos;
+
+ BIO *wbio;
+ char *wbio_buf;
+ size_t wbio_size;
+ size_t wbio_pos;
+};
+
+
+static int
+http_client_redirected ( http_client_t *hc );
+static int
+http_client_ssl_write_update( http_client_t *hc );
+static int
+http_client_reconnect
+ ( http_client_t *hc, http_ver_t ver, const char *scheme,
+ const char *host, int port );
+#if HTTPCLIENT_TESTSUITE
+static void
+http_client_testsuite_run( void );
+#endif
+
+
+/*
+ * Global state
+ */
+static int http_running;
+static tvhpoll_t *http_poll;
+static TAILQ_HEAD(,http_client) http_clients;
+static pthread_mutex_t http_lock;
+static th_pipe_t http_pipe;
+
+/*
+ *
+ */
+static int
+http_port( const char *scheme, int port )
+{
+ if (port <= 0 || port > 65535) {
+ if (strcmp(scheme, "http") == 0)
+ port = 80;
+ else if (strcmp(scheme, "https") == 0)
+ port = 443;
+ else if (strcmp(scheme, "rtsp") == 0)
+ port = 554;
+ else {
+ tvhlog(LOG_ERR, "httpc", "Unknown scheme '%s'", scheme);
+ return -EINVAL;
+ }
+ }
+ return port;
+}
+
+/*
+ * Disable
+ */
+static void
+http_client_shutdown ( http_client_t *hc, int force )
+{
+ struct http_client_ssl *ssl = hc->hc_ssl;
+ tvhpoll_t *efd = NULL;
+
+ hc->hc_shutdown = 1;
+ if (ssl) {
+ if (!ssl->shutdown) {
+ SSL_shutdown(hc->hc_ssl->ssl);
+ http_client_ssl_write_update(hc);
+ ssl->shutdown = 1;
+ }
+ if (!force)
+ return;
+ }
+ if (hc->hc_efd) {
+ tvhpoll_event_t ev;
+ if (hc->hc_efd == http_poll)
+ TAILQ_REMOVE(&http_clients, hc, hc_link);
+ memset(&ev, 0, sizeof(ev));
+ ev.fd = hc->hc_fd;
+ tvhpoll_rem(efd = hc->hc_efd, &ev, 1);
+ hc->hc_efd = NULL;
+ }
+ if (hc->hc_fd >= 0) {
+ if (hc->hc_conn_closed)
+ hc->hc_conn_closed(hc, -hc->hc_result);
+ if (hc->hc_fd >= 0)
+ close(hc->hc_fd);
+ hc->hc_fd = -1;
+ }
+}
+
+/*
+ * Poll I/O
+ */
+static void
+http_client_poll_dir ( http_client_t *hc, int in, int out )
+{
+ int events = (in ? TVHPOLL_IN : 0) | (out ? TVHPOLL_OUT : 0);
+ if (hc->hc_efd && hc->hc_pevents != events) {
+ tvhpoll_event_t ev;
+ memset(&ev, 0, sizeof(ev));
+ ev.fd = hc->hc_fd;
+ ev.events = events | TVHPOLL_IN;
+ ev.data.ptr = hc;
+ tvhpoll_add(hc->hc_efd, &ev, 1);
+ }
+ hc->hc_pevents = events;
+ /* make sure to se the correct errno for our SSL routines */
+ errno = EAGAIN;
+}
+
+static void
+http_client_direction ( http_client_t *hc, int sending )
+{
+ hc->hc_sending = sending;
+ if (hc->hc_ssl == NULL)
+ http_client_poll_dir(hc, 1, sending);
+}
+
+/*
+ * Main I/O routines
+ */
+
+static void
+http_client_cmd_destroy( http_client_t *hc, http_client_wcmd_t *cmd )
+{
+ TAILQ_REMOVE(&hc->hc_wqueue, cmd, link);
+ free(cmd->wbuf);
+ free(cmd);
+}
+
+static int
+http_client_flush( http_client_t *hc, int result )
+{
+ if (result < 0)
+ http_client_shutdown(hc, 0);
+ hc->hc_result = result;
+ hc->hc_in_data = 0;
+ hc->hc_hsize = 0;
+ hc->hc_csize = 0;
+ hc->hc_rpos = 0;
+ hc->hc_chunked = 0;
+ free(hc->hc_chunk);
+ hc->hc_chunk = 0;
+ hc->hc_chunk_pos = 0;
+ hc->hc_chunk_size = 0;
+ hc->hc_chunk_csize = 0;
+ hc->hc_chunk_alloc = 0;
+ hc->hc_chunk_trails = 0;
+ http_arg_flush(&hc->hc_args);
+ return result;
+}
+
+int
+http_client_clear_state( http_client_t *hc )
+{
+ if (hc->hc_shutdown)
+ return -EBADFD;
+ free(hc->hc_data);
+ hc->hc_data = NULL;
+ hc->hc_data_size = 0;
+ return http_client_flush(hc, 0);
+}
+
+static int
+http_client_ssl_read_update( http_client_t *hc )
+{
+ struct http_client_ssl *ssl = hc->hc_ssl;
+ char *rbuf = alloca(hc->hc_io_size);
+ ssize_t r, r2;
+ size_t len;
+
+ if (ssl->rbio_pos > 0) {
+ r = BIO_write(ssl->rbio, ssl->rbio_buf, ssl->rbio_pos);
+ if (r >= 0) {
+ memmove(ssl->rbio_buf, ssl->rbio_buf + r, ssl->rbio_pos - r);
+ ssl->rbio_pos -= r;
+ } else if (r < 0) {
+ errno = EIO;
+ return -1;
+ }
+ }
+ r = recv(hc->hc_fd, rbuf, hc->hc_io_size, MSG_DONTWAIT);
+ if (r == 0) {
+ errno = ESTRPIPE;
+ return -1;
+ }
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
+ http_client_poll_dir(hc, 1, 0);
+ errno = EAGAIN;
+ return r;
+ }
+ return r;
+ }
+ r2 = BIO_write(ssl->rbio, rbuf, r);
+ len = r - (r2 < 0 ? 0 : r2);
+ if (len) {
+ if (ssl->rbio_pos + len > ssl->rbio_size) {
+ ssl->rbio_buf = realloc(ssl->rbio_buf, ssl->rbio_pos + len);
+ ssl->rbio_size += len;
+ }
+ memcpy(ssl->rbio_buf + ssl->rbio_pos, rbuf + (len - r), len);
+ ssl->rbio_pos += len;
+ }
+ return 0;
+}
+
+static int
+http_client_ssl_write_update( http_client_t *hc )
+{
+ struct http_client_ssl *ssl = hc->hc_ssl;
+ char *rbuf = alloca(hc->hc_io_size);
+ ssize_t r, r2;
+ size_t len;
+
+ if (ssl->wbio_pos) {
+ r = send(hc->hc_fd, ssl->wbio_buf, ssl->wbio_pos, MSG_DONTWAIT);
+ if (r > 0) {
+ memmove(ssl->wbio_buf, ssl->wbio_buf + r, ssl->wbio_pos - r);
+ ssl->wbio_pos -= r;
+ } else if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
+ http_client_poll_dir(hc, 0, 1);
+ errno = EAGAIN;
+ return r;
+ }
+ return r;
+ }
+ if (ssl->wbio_pos)
+ return 1;
+ }
+ r = BIO_read(ssl->wbio, rbuf, hc->hc_io_size);
+ if (r > 0) {
+ r2 = send(hc->hc_fd, rbuf, r, MSG_DONTWAIT);
+ len = r - (r2 < 0 ? 0 : r2);
+ if (len) {
+ if (ssl->wbio_pos + len > ssl->wbio_size) {
+ ssl->wbio_buf = realloc(ssl->wbio_buf, ssl->wbio_pos + len);
+ ssl->wbio_size += len;
+ }
+ memcpy(ssl->wbio_buf + ssl->wbio_pos, rbuf + (len - r), len);
+ ssl->wbio_pos += len;
+ }
+ if (r2 < 0) {
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
+ http_client_poll_dir(hc, 0, 1);
+ errno = EAGAIN;
+ return r2;
+ }
+ return r2;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static ssize_t
+http_client_ssl_recv( http_client_t *hc, void *buf, size_t len )
+{
+ ssize_t r;
+ int e;
+
+ while (1) {
+ r = SSL_read(hc->hc_ssl->ssl, buf, len);
+ if (r > 0)
+ return r;
+ e = SSL_get_error(hc->hc_ssl->ssl, r);
+ if (e == SSL_ERROR_WANT_READ) {
+ r = http_client_ssl_write_update(hc);
+ if (r < 0)
+ return r;
+ r = http_client_ssl_read_update(hc);
+ if (r < 0)
+ return r;
+ } else if (e == SSL_ERROR_WANT_WRITE) {
+ r = http_client_ssl_write_update(hc);
+ if (r < 0)
+ return r;
+ } else if (e == SSL_ERROR_ZERO_RETURN) {
+ errno = ESTRPIPE;
+ return -1;
+ } else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) {
+ errno = EBADFD;
+ return -1;
+ } else if (e == SSL_ERROR_SSL) {
+ errno = EPERM;
+ return -1;
+ } else {
+ errno = EIO;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+http_client_ssl_send( http_client_t *hc, const void *buf, size_t len )
+{
+ struct http_client_ssl *ssl = hc->hc_ssl;
+ ssize_t r, r2;
+ int e;
+
+ while (1) {
+ if (!ssl->connected) {
+ r = SSL_connect(ssl->ssl);
+ if (r > 0) {
+ ssl->connected = 1;
+ goto write;
+ }
+ } else {
+write:
+ r = SSL_write(ssl->ssl, buf, len);
+ }
+ if (r > 0) {
+ while (1) {
+ r2 = http_client_ssl_write_update(hc);
+ if (r2 < 0) {
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
+ break;
+ return r2;
+ }
+ if (r2 == 0)
+ break;
+ }
+ return r;
+ }
+ e = SSL_get_error(ssl->ssl, r);
+ ERR_print_errors_fp(stdout);
+ if (e == SSL_ERROR_WANT_READ) {
+ r = http_client_ssl_write_update(hc);
+ if (r < 0)
+ return r;
+ r = http_client_ssl_read_update(hc);
+ if (r < 0)
+ return r;
+ } else if (e == SSL_ERROR_WANT_WRITE) {
+ r = http_client_ssl_write_update(hc);
+ if (r < 0)
+ return r;
+ } else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) {
+ errno = EBADFD;
+ return -1;
+ } else if (e == SSL_ERROR_SSL) {
+ errno = EPERM;
+ return -1;
+ } else {
+ errno = EIO;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+http_client_ssl_shutdown( http_client_t *hc )
+{
+ ssize_t r;
+ int e;
+
+ while (1) {
+ r = SSL_shutdown(hc->hc_ssl->ssl);
+ if (r > 0) {
+ /* everything done, bail-out completely */
+ http_client_shutdown(hc, 1);
+ return r;
+ }
+ e = SSL_get_error(hc->hc_ssl->ssl, r);
+ if (e == SSL_ERROR_WANT_READ) {
+ r = http_client_ssl_write_update(hc);
+ if (r < 0)
+ return r;
+ r = http_client_ssl_read_update(hc);
+ if (r < 0)
+ return r;
+ } else if (e == SSL_ERROR_WANT_WRITE) {
+ r = http_client_ssl_write_update(hc);
+ if (r < 0)
+ return r;
+ } else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) {
+ errno = EBADFD;
+ return -1;
+ } else if (r == SSL_ERROR_SSL) {
+ errno = EPERM;
+ return -1;
+ } else {
+ errno = EIO;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+http_client_send_partial( http_client_t *hc )
+{
+ http_client_wcmd_t *wcmd;
+ ssize_t r;
+ int res = HTTP_CON_IDLE;
+
+ wcmd = TAILQ_FIRST(&hc->hc_wqueue);
+ while (wcmd != NULL) {
+ hc->hc_cmd = wcmd->wcmd;
+ hc->hc_rcseq = wcmd->wcseq;
+ if (hc->hc_ssl)
+ r = http_client_ssl_send(hc, wcmd->wbuf + wcmd->wpos,
+ wcmd->wsize - wcmd->wpos);
+ else
+ r = send(hc->hc_fd, wcmd->wbuf + wcmd->wpos,
+ wcmd->wsize - wcmd->wpos, MSG_DONTWAIT);
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK ||
+ errno == EINPROGRESS) {
+ http_client_direction(hc, 1);
+ return HTTP_CON_SENDING;
+ }
+ return http_client_flush(hc, -errno);
+ }
+ wcmd->wpos += r;
+ if (wcmd->wpos >= wcmd->wsize) {
+ http_client_cmd_destroy(hc, wcmd);
+ res = HTTP_CON_SENT;
+ wcmd = NULL;
+ }
+ break;
+ }
+ if (wcmd == NULL) {
+ http_client_direction(hc, 0);
+ return res;
+ } else {
+ http_client_direction(hc, 1);
+ return HTTP_CON_SENDING;
+ }
+}
+
+int
+http_client_send( http_client_t *hc, enum http_cmd cmd,
+ const char *path, const char *query,
+ http_arg_list_t *header, void *body, size_t body_size )
+{
+ http_client_wcmd_t *wcmd = calloc(1, sizeof(*wcmd));
+ http_arg_t *h;
+ htsbuf_queue_t q;
+ const char *s;
+
+ if (hc->hc_shutdown) {
+ if (header)
+ http_arg_flush(header);
+ return -EIO;
+ }
+
+ wcmd->wcmd = cmd;
+ hc->hc_keepalive = 1;
+
+ htsbuf_queue_init(&q, 0);
+ s = http_cmd2str(cmd);
+ if (s == NULL) {
+ http_arg_flush(header);
+ return -EINVAL;
+ }
+ htsbuf_append(&q, s, strlen(s));
+ htsbuf_append(&q, " ", 1);
+ if (path == NULL || path[0] == '\0')
+ path = "/";
+ htsbuf_append(&q, path, strlen(path));
+ if (query && query[0] != '\0') {
+ htsbuf_append(&q, "?", 1);
+ htsbuf_append(&q, query, strlen(query));
+ }
+ htsbuf_append(&q, " ", 1);
+ s = http_ver2str(hc->hc_version);
+ if (s == NULL) {
+ htsbuf_queue_flush(&q);
+ http_arg_flush(header);
+ return -EINVAL;
+ }
+ htsbuf_append(&q, s, strlen(s));
+ htsbuf_append(&q, "\r\n", 2);
+
+ if (header) {
+ TAILQ_FOREACH(h, header, link) {
+ htsbuf_append(&q, h->key, strlen(h->key));
+ htsbuf_append(&q, ": ", 2);
+ htsbuf_append(&q, h->val, strlen(h->val));
+ htsbuf_append(&q, "\r\n", 2);
+ if (strcasecmp(h->key, "Connection") == 0 &&
+ strcasecmp(h->val, "close") == 0)
+ hc->hc_keepalive = 0;
+ }
+ http_arg_flush(header);
+ }
+
+ if (hc->hc_version == HTTP_VERSION_1_0)
+ hc->hc_keepalive = 0;
+ if (hc->hc_version == RTSP_VERSION_1_0) {
+ hc->hc_cseq = (hc->hc_cseq + 1) & 0x7fff;
+ htsbuf_qprintf(&q, "CSeq: %i\r\n", hc->hc_cseq);
+ wcmd->wcseq = hc->hc_cseq;
+ }
+ htsbuf_append(&q, "\r\n", 2);
+ if (body && body_size)
+ htsbuf_append(&q, body, body_size);
+
+ body_size = q.hq_size;
+ body = malloc(body_size);
+ htsbuf_read(&q, body, body_size);
+
+#if ENABLE_TRACE
+ tvhtrace("httpc", "sending %s cmd", http_ver2str(hc->hc_version));
+ tvhlog_hexdump("httpc", body, body_size);
+#endif
+
+ wcmd->wbuf = body;
+ wcmd->wsize = body_size;
+
+ TAILQ_INSERT_TAIL(&hc->hc_wqueue, wcmd, link);
+
+ hc->hc_ping_time = dispatch_clock;
+
+ return http_client_send_partial(hc);
+}
+
+static int
+http_client_finish( http_client_t *hc )
+{
+ int res;
+
+#if ENABLE_TRACE
+ if (hc->hc_data) {
+ tvhtrace("httpc", "received %s data", http_ver2str(hc->hc_version));
+ tvhlog_hexdump("httpc", hc->hc_data, hc->hc_csize);
+ }
+#endif
+ if (hc->hc_data_complete) {
+ res = hc->hc_data_complete(hc);
+ if (res < 0)
+ return http_client_flush(hc, res);
+ }
+ hc->hc_hsize = hc->hc_csize = 0;
+ if (hc->hc_handle_location &&
+ (hc->hc_code == HTTP_STATUS_MOVED ||
+ hc->hc_code == HTTP_STATUS_FOUND ||
+ hc->hc_code == HTTP_STATUS_SEE_OTHER ||
+ hc->hc_code == HTTP_STATUS_NOT_MODIFIED)) {
+ const char *p = http_arg_get(&hc->hc_args, "Location");
+ if (p) {
+ hc->hc_location = strdup(p);
+ res = http_client_redirected(hc);
+ if (res < 0)
+ return http_client_flush(hc, res);
+ return HTTP_CON_RECEIVING;
+ }
+ }
+ if (TAILQ_FIRST(&hc->hc_wqueue) && hc->hc_code == HTTP_STATUS_OK)
+ return http_client_send_partial(hc);
+ if (!hc->hc_keepalive) {
+ http_client_shutdown(hc, 0);
+ if (hc->hc_ssl) {
+ /* finish the shutdown I/O sequence, notify owner later */
+ errno = EAGAIN;
+ return HTTP_CON_RECEIVING;
+ }
+ }
+ return hc->hc_reconnected ? HTTP_CON_RECEIVING : HTTP_CON_DONE;
+}
+
+static int
+http_client_parse_arg( http_arg_list_t *list, const char *p )
+{
+ char *d, *t;
+
+ d = strchr(p, ':');
+ if (d) {
+ *d++ = '\0';
+ while (*d && *d <= ' ')
+ d++;
+ t = d + strlen(d);
+ while (--t != d && *t <= ' ')
+ *t = '\0';
+ http_arg_set(list, p, d);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int
+http_client_data_copy( http_client_t *hc, char *buf, size_t len )
+{
+ int res;
+
+ if (hc->hc_data_received) {
+ res = hc->hc_data_received(hc, buf, len);
+ if (res < 0)
+ return res;
+ } else {
+ hc->hc_data = realloc(hc->hc_data, hc->hc_data_size + len + 1);
+ memcpy(hc->hc_data + hc->hc_data_size, buf, len);
+ hc->hc_data_size += len;
+ hc->hc_data[hc->hc_data_size] = '\0';
+ }
+ return 0;
+}
+
+static ssize_t
+http_client_data_chunked( http_client_t *hc, char *buf, size_t len, int *end )
+{
+ size_t old = len, l, l2;
+ char *d, *s;
+ int res;
+
+ while (len > 0) {
+ if (hc->hc_chunk_size) {
+ s = hc->hc_chunk;
+ l = len;
+ if (hc->hc_chunk_pos + l > hc->hc_chunk_size)
+ l = hc->hc_chunk_size - hc->hc_chunk_pos;
+ memcpy(s + hc->hc_chunk_pos, buf, l);
+ hc->hc_chunk_pos += l;
+ buf += l;
+ len -= l;
+ if (hc->hc_chunk_pos >= hc->hc_chunk_size) {
+ if (s[hc->hc_chunk_size - 2] != '\r' &&
+ s[hc->hc_chunk_size - 1] != '\n')
+ return -EIO;
+ res = http_client_data_copy(hc, hc->hc_chunk, hc->hc_chunk_size - 2);
+ if (res < 0)
+ return res;
+ hc->hc_chunk_size = hc->hc_chunk_pos = 0;
+ }
+ continue;
+ }
+ l = 0;
+ if (hc->hc_chunk_csize) {
+ s = d = hc->hc_chunk;
+ if (buf[0] == '\n' && s[hc->hc_chunk_csize-1] == '\r')
+ l = 1;
+ else if (len > 1 && buf[0] == '\r' && buf[1] == '\n')
+ l = 2;
+ } else {
+ d = strstr(s = buf, "\r\n");
+ if (d) {
+ *d = '\0';
+ l = (d + 2) - s;
+ }
+ }
+ if (l) {
+ hc->hc_chunk_csize = 0;
+ if (hc->hc_chunk_trails) {
+ buf += l;
+ len -= l;
+ if (s[0] == '\0') {
+ *end = 1;
+ return old - len;
+ }
+ res = http_client_parse_arg(&hc->hc_args, s);
+ if (res < 0)
+ return res;
+ continue;
+ }
+ if (s[0] == '0' && s[1] == '\0')
+ hc->hc_chunk_trails = 1;
+ else {
+ hc->hc_chunk_size = strtoll(s, NULL, 16);
+ if (hc->hc_chunk_size == 0)
+ return -EIO;
+ if (hc->hc_chunk_size > 256*1024)
+ return -EMSGSIZE;
+ hc->hc_chunk_size += 2; /* CR-LF */
+ if (hc->hc_chunk_alloc < hc->hc_chunk_size) {
+ hc->hc_chunk = realloc(hc->hc_chunk, hc->hc_chunk_size + 1);
+ hc->hc_chunk[hc->hc_chunk_size] = '\0';
+ hc->hc_chunk_alloc = hc->hc_chunk_size;
+ }
+ }
+ buf += l;
+ len -= l;
+ } else {
+ l2 = hc->hc_chunk_csize + len;
+ if (l2 > hc->hc_chunk_alloc) {
+ hc->hc_chunk = realloc(hc->hc_chunk, l2 + 1);
+ hc->hc_chunk[l2] = '\0';
+ hc->hc_chunk_alloc = l2;
+ }
+ memcpy(hc->hc_chunk + hc->hc_chunk_csize, buf, len);
+ hc->hc_chunk_csize += len;
+ buf += len;
+ len -= len;
+ }
+ }
+ return old;
+}
+
+static int
+http_client_data_received( http_client_t *hc, char *buf, ssize_t len )
+{
+ ssize_t l, l2, csize;
+ int res, end = 0;
+
+ buf[len] = '\0';
+
+ if (len == 0) {
+ if (hc->hc_csize == -1 || hc->hc_rpos >= hc->hc_csize)
+ return 1;
+ return 0;
+ }
+
+ csize = hc->hc_csize < 0 ? 0 : hc->hc_csize;
+ l = len;
+ if (hc->hc_csize && hc->hc_csize != -1 && hc->hc_rpos > csize) {
+ l2 = hc->hc_rpos - csize;
+ if (l2 < l)
+ l = l2;
+ }
+ if (l) {
+ if (hc->hc_chunked) {
+ l = http_client_data_chunked(hc, buf, l, &end);
+ if (l < 0)
+ return l;
+ } else {
+ res = http_client_data_copy(hc, buf, l);
+ if (res < 0)
+ return res;
+ }
+ }
+ hc->hc_rpos += l;
+ end |= hc->hc_csize && hc->hc_rpos >= hc->hc_csize;
+ if (l < len) {
+ l2 = len - l;
+ if (l2 > hc->hc_rsize)
+ hc->hc_rbuf = realloc(hc->hc_rbuf, hc->hc_rsize = l2 + 1);
+ memcpy(hc->hc_rbuf, buf + l, l2);
+ hc->hc_rbuf[l2] = '\0';
+ hc->hc_rpos = l2;
+ }
+ return end ? 1 : 0;
+}
+
+int
+http_client_run( http_client_t *hc )
+{
+ char *buf, *saveptr, *argv[3], *d, *p;
+ http_ver_t ver;
+ ssize_t r;
+ size_t len;
+ int res;
+
+ if (hc == NULL)
+ return 0;
+
+ if (hc->hc_shutdown) {
+ if (hc->hc_ssl && hc->hc_ssl->shutdown) {
+ r = http_client_ssl_shutdown(hc);
+ if (r < 0) {
+ if (errno != ESTRPIPE) {
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
+ return HTTP_CON_SENDING;
+ return r;
+ }
+ }
+ if (r == 0)
+ return HTTP_CON_SENDING;
+ }
+ return hc->hc_result ? hc->hc_result : HTTP_CON_DONE;
+ }
+
+ if (hc->hc_sending) {
+ res = http_client_send_partial(hc);
+ if (res < 0 || res == HTTP_CON_SENDING)
+ return res;
+ }
+
+ buf = alloca(hc->hc_io_size);
+
+ if (!hc->hc_in_data && hc->hc_rpos > 3 &&
+ (d = strstr(hc->hc_rbuf, "\r\n\r\n")) != NULL)
+ goto header;
+
+retry:
+ if (hc->hc_ssl)
+ r = http_client_ssl_recv(hc, buf, hc->hc_io_size);
+ else
+ r = recv(hc->hc_fd, buf, hc->hc_io_size, MSG_DONTWAIT);
+ if (r == 0) {
+ if (hc->hc_in_data && !hc->hc_keepalive)
+ return http_client_finish(hc);
+ return http_client_flush(hc, -ESTRPIPE);
+ }
+ if (r < 0) {
+ if (errno == ESTRPIPE && hc->hc_in_data && !hc->hc_keepalive)
+ return http_client_finish(hc);
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+ return HTTP_CON_RECEIVING;
+ return http_client_flush(hc, -errno);
+ }
+#if ENABLE_TRACE
+ if (r > 0) {
+ tvhtrace("httpc", "received %s answer", http_ver2str(hc->hc_version));
+ tvhlog_hexdump("httpc", buf, r);
+ }
+#endif
+
+ if (hc->hc_in_data) {
+ res = http_client_data_received(hc, buf, r);
+ if (res < 0)
+ return http_client_flush(hc, res);
+ if (res > 0)
+ return http_client_finish(hc);
+ if (hc->hc_data_limit && r + hc->hc_rsize >= hc->hc_data_limit)
+ return http_client_flush(hc, -EOVERFLOW);
+ goto retry;
+ }
+
+ if (hc->hc_rsize < r + hc->hc_rpos) {
+ if (hc->hc_rsize + r > 16*1024)
+ return http_client_flush(hc, -EMSGSIZE);
+ hc->hc_rsize += r;
+ hc->hc_rbuf = realloc(hc->hc_rbuf, hc->hc_rsize + 1);
+ }
+ memcpy(hc->hc_rbuf + hc->hc_rpos, buf, r);
+ hc->hc_rpos += r;
+ hc->hc_rbuf[hc->hc_rpos] = '\0';
+
+next_header:
+ if (hc->hc_rpos < 3)
+ return HTTP_CON_RECEIVING;
+ if ((d = strstr(hc->hc_rbuf, "\r\n\r\n")) == NULL)
+ return HTTP_CON_RECEIVING;
+
+header:
+ *d = '\0';
+ len = hc->hc_rpos;
+ hc->hc_reconnected = 0;
+ http_client_clear_state(hc);
+ hc->hc_rpos = len;
+ hc->hc_hsize = d - hc->hc_rbuf + 4;
+ p = strtok_r(hc->hc_rbuf, "\r\n", &saveptr);
+ if (p == NULL)
+ return http_client_flush(hc, -EINVAL);
+ tvhtrace("httpc", "%s answer '%s'", http_ver2str(hc->hc_version), p);
+ if (http_tokenize(p, argv, 3, -1) != 3)
+ return http_client_flush(hc, -EINVAL);
+ if ((ver = http_str2ver(argv[0])) < 0)
+ return http_client_flush(hc, -EINVAL);
+ if (ver != hc->hc_version)
+ return http_client_flush(hc, -EINVAL);
+ if ((hc->hc_code = atoi(argv[1])) < 200)
+ return http_client_flush(hc, -EINVAL);
+ while ((p = strtok_r(NULL, "\r\n", &saveptr)) != NULL) {
+ res = http_client_parse_arg(&hc->hc_args, p);
+ if (res < 0)
+ return http_client_flush(hc, -EINVAL);
+ }
+ p = http_arg_get(&hc->hc_args, "Content-Length");
+ if (p) {
+ hc->hc_csize = atoll(p);
+ if (hc->hc_csize == 0)
+ hc->hc_csize = -1;
+ }
+ p = http_arg_get(&hc->hc_args, "Connection");
+ if (p) {
+ if (hc->hc_keepalive && strcasecmp(p, "keep-alive"))
+ return http_client_flush(hc, -EINVAL);
+ if (!hc->hc_keepalive && strcasecmp(p, "close"))
+ return http_client_flush(hc, -EINVAL);
+ }
+ if (ver == RTSP_VERSION_1_0) {
+ p = http_arg_get(&hc->hc_args, "CSeq");
+ if (p == NULL || hc->hc_rcseq != atoi(p))
+ return http_client_flush(hc, -EINVAL);
+ }
+ p = http_arg_get(&hc->hc_args, "Transfer-Encoding");
+ if (p)
+ hc->hc_chunked = strcasecmp(p, "chunked") == 0;
+ if (hc->hc_hdr_received) {
+ res = hc->hc_hdr_received(hc);
+ if (res < 0)
+ return http_client_flush(hc, res);
+ }
+ hc->hc_rpos -= hc->hc_hsize;
+ len = hc->hc_rpos;
+ if (hc->hc_code == HTTP_STATUS_CONTINUE) {
+ memmove(hc->hc_rbuf, hc->hc_rbuf + hc->hc_hsize, len);
+ goto next_header;
+ }
+ hc->hc_rpos = 0;
+ hc->hc_in_data = 1;
+ res = http_client_data_received(hc, hc->hc_rbuf + hc->hc_hsize, len);
+ if (res < 0)
+ return http_client_flush(hc, res);
+ if (res > 0)
+ return http_client_finish(hc);
+ goto retry;
+}
+
+/*
+ * Redirected
+ */
+static void
+http_client_basic_args ( http_arg_list_t *h, const url_t *url, int keepalive )
+{
+ char buf[64];
+
+ http_arg_init(h);
+ http_arg_set(h, "Host", url->host);
+ snprintf(buf, sizeof(buf), "TVHeadend/%s", tvheadend_version);
+ http_arg_set(h, "User-Agent", buf);
+ if (!keepalive)
+ http_arg_set(h, "Connection", "close");
+ if (url->user && url->user[0] && url->pass && url->pass[0]) {
+ size_t plen = strlen(url->pass);
+ size_t ulen = strlen(url->user);
+ size_t len = BASE64_SIZE(plen) + 1;
+ char *buf = alloca(ulen + 1 + len + 1);
+ strcpy(buf, url->user);
+ base64_encode(buf + ulen + 1, len, (uint8_t *)url->pass, plen);
+ buf[ulen] = ':';
+ http_arg_set(h, "Authorization", buf);
+ }
+}
+
+static int
+http_client_redirected ( http_client_t *hc )
+{
+ char *location, *location2;
+ http_arg_list_t h;
+ tvhpoll_t *efd;
+ url_t u;
+ int r;
+
+ if (++hc->hc_redirects > 10)
+ return -ELOOP;
+
+ location = hc->hc_location;
+ location2 = hc->hc_location = NULL;
+
+ if (location[0] == '\0' || location[0] == '/') {
+ size_t size2 = strlen(hc->hc_scheme) + 3 + strlen(hc->hc_host) +
+ 12 + strlen(location) + 1;
+ location2 = alloca(size2);
+ snprintf(location2, size2, "%s://%s:%i%s",
+ hc->hc_scheme, hc->hc_host, hc->hc_port, location);
+ }
+
+ memset(&u, 0, sizeof(u));
+ if (urlparse(location2 ? location2 : location, &u)) {
+ tvherror("httpc", "redirection - cannot parse url '%s'",
+ location2 ? location2 : location);
+ free(location);
+ return -EIO;
+ }
+ free(location);
+
+ if (strcmp(u.scheme, hc->hc_scheme) ||
+ strcmp(u.host, hc->hc_host) ||
+ http_port(u.scheme, u.port) != hc->hc_port ||
+ !hc->hc_keepalive) {
+ efd = hc->hc_efd;
+ http_client_shutdown(hc, 1);
+ r = http_client_reconnect(hc, hc->hc_version,
+ u.scheme, u.host, u.port);
+ if (r < 0) {
+ urlreset(&u);
+ return r;
+ }
+ hc->hc_efd = efd;
+ }
+
+ http_client_flush(hc, 0);
+
+ http_client_basic_args(&h, &u, hc->hc_keepalive);
+ hc->hc_reconnected = 1;
+ hc->hc_shutdown = 0;
+ hc->hc_pevents = 0;
+
+ r = http_client_send(hc, hc->hc_cmd, u.path, u.query, &h, NULL, 0);
+ if (r < 0) {
+ urlreset(&u);
+ return r;
+ }
+
+ hc->hc_reconnected = 1;
+ urlreset(&u);
+ return 1;
+}
+
+int
+http_client_simple( http_client_t *hc, const url_t *url )
+{
+ http_arg_list_t h;
+
+ http_client_basic_args(&h, url, 0);
+ return http_client_send(hc, HTTP_CMD_GET, url->path, url->query,
+ &h, NULL, 0);
+}
+
+/*
+ * Data thread
+ */
+static void *
+http_client_thread ( void *p )
+{
+ int n;
+ tvhpoll_event_t ev;
+ http_client_t *hc;
+ char c;
+
+ while (http_running) {
+ n = tvhpoll_wait(http_poll, &ev, 1, -1);
+ if (n < 0) {
+ if (http_running &&
+ errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK)
+ tvherror("httpc", "tvhpoll_wait() error");
+ } else if (n > 0) {
+ if (&http_pipe == ev.data.ptr) {
+ if (read(http_pipe.rd, &c, 1) == 1) {
+ /* end-of-task */
+ break;
+ }
+ continue;
+ }
+ pthread_mutex_lock(&http_lock);
+ TAILQ_FOREACH(hc, &http_clients, hc_link)
+ if (hc == ev.data.ptr)
+ break;
+ http_client_run(hc);
+ pthread_mutex_unlock(&http_lock);
+ }
+ }
+
+ return NULL;
+}
+
+static void
+http_client_ssl_free( http_client_t *hc )
+{
+ struct http_client_ssl *ssl;
+
+ if ((ssl = hc->hc_ssl) != NULL) {
+ free(ssl->rbio_buf);
+ free(ssl->wbio_buf);
+ SSL_free(ssl->ssl);
+ SSL_CTX_free(ssl->ctx);
+ free(ssl);
+ hc->hc_ssl = NULL;
+ }
+}
+
+/*
+ * Setup a connection (async)
+ */
+static int
+http_client_reconnect
+ ( http_client_t *hc, http_ver_t ver, const char *scheme,
+ const char *host, int port )
+{
+ struct http_client_ssl *ssl;
+ char errbuf[256];
+
+ free(hc->hc_scheme);
+ free(hc->hc_host);
+
+ port = http_port(scheme, port);
+ hc->hc_pevents = 0;
+ hc->hc_version = ver;
+ hc->hc_scheme = strdup(scheme);
+ hc->hc_host = strdup(host);
+ hc->hc_port = port;
+ hc->hc_fd = tcp_connect(host, port, errbuf, sizeof(errbuf), -1);
+ if (hc->hc_fd < 0) {
+ tvhlog(LOG_ERR, "httpc", "Unable to connect to %s:%i - %s", host, port, errbuf);
+ return -EINVAL;
+ }
+ http_client_ssl_free(hc);
+ if (strcasecmp(scheme, "https") == 0 || strcasecmp(scheme, "rtsps") == 0) {
+ ssl = calloc(1, sizeof(*ssl));
+ hc->hc_ssl = ssl;
+ ssl->ctx = SSL_CTX_new(SSLv23_client_method());
+ if (ssl->ctx == NULL) {
+ tvhlog(LOG_ERR, "httpc", "Unable to get SSL_CTX");
+ goto err1;
+ }
+ /* do not use SSLv2 */
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION);
+ /* adjust cipher list */
+ if (SSL_CTX_set_cipher_list(ssl->ctx, "HIGH:MEDIUM") != 1) {
+ tvhlog(LOG_ERR, "httpc", "Unable to adjust SSL cipher list");
+ goto err2;
+ }
+ ssl->rbio = BIO_new(BIO_s_mem());
+ ssl->wbio = BIO_new(BIO_s_mem());
+ ssl->ssl = SSL_new(ssl->ctx);
+ if (ssl->ssl == NULL || ssl->rbio == NULL || ssl->wbio == NULL) {
+ tvhlog(LOG_ERR, "httpc", "Unable to get SSL handle");
+ goto err3;
+ }
+ SSL_set_bio(ssl->ssl, ssl->rbio, ssl->wbio);
+ if (!SSL_set_tlsext_host_name(ssl->ssl, host)) {
+ tvhlog(LOG_ERR, "httpc", "Unable to set SSL hostname");
+ goto err4;
+ }
+ }
+
+ return 0;
+
+err4:
+ SSL_free(ssl->ssl);
+err3:
+ BIO_free(ssl->rbio);
+ BIO_free(ssl->wbio);
+err2:
+ SSL_CTX_free(ssl->ctx);
+err1:
+ close(hc->hc_fd);
+ free(ssl);
+ return -EINVAL;
+}
+
+http_client_t *
+http_client_connect
+ ( void *aux, http_ver_t ver, const char *scheme, const char *host, int port )
+{
+ http_client_t *hc;
+
+ hc = calloc(1, sizeof(http_client_t));
+ hc->hc_aux = aux;
+ hc->hc_io_size = 1024;
+
+ TAILQ_INIT(&hc->hc_args);
+ TAILQ_INIT(&hc->hc_wqueue);
+
+ if (http_client_reconnect(hc, ver, scheme, host, port) < 0) {
+ free(hc);
+ return NULL;
+ }
+
+ return hc;
+}
+
+/*
+ * Register to the another thread
+ */
+void
+http_client_register( http_client_t *hc )
+{
+ assert(hc->hc_data_received || hc->hc_conn_closed);
+ assert(hc->hc_efd == NULL);
+
+ pthread_mutex_lock(&http_lock);
+
+ TAILQ_INSERT_TAIL(&http_clients, hc, hc_link);
+
+ hc->hc_efd = http_poll;
+
+ pthread_mutex_unlock(&http_lock);
+}
+
+/*
+ * Cancel
+ */
+void
+http_client_close ( http_client_t *hc )
+{
+ http_client_wcmd_t *wcmd;
+
+ if (hc == NULL)
+ return;
+
+ pthread_mutex_lock(&http_lock);
+ http_client_shutdown(hc, 1);
+ http_client_flush(hc, 0);
+ pthread_mutex_unlock(&http_lock);
+ while ((wcmd = TAILQ_FIRST(&hc->hc_wqueue)) != NULL)
+ http_client_cmd_destroy(hc, wcmd);
+ http_client_ssl_free(hc);
+ free(hc->hc_location);
+ free(hc->hc_rbuf);
+ free(hc->hc_data);
+ free(hc->hc_host);
+ free(hc->hc_scheme);
+ free(hc);
+}
+
+/*
+ * Initialise subsystem
+ */
+pthread_t http_client_tid;
+
+void
+http_client_init ( void )
+{
+ tvhpoll_event_t ev;
+
+ /* Setup list */
+ pthread_mutex_init(&http_lock, NULL);
+ TAILQ_INIT(&http_clients);
+
+ /* Setup pipe */
+ tvh_pipe(O_NONBLOCK, &http_pipe);
+
+ /* Setup poll */
+ http_poll = tvhpoll_create(10);
+ memset(&ev, 0, sizeof(ev));
+ ev.fd = http_pipe.rd;
+ ev.events = TVHPOLL_IN;
+ ev.data.ptr = &http_pipe;
+ tvhpoll_add(http_poll, &ev, 1);
+
+ /* Setup thread */
+ http_running = 1;
+ tvhthread_create(&http_client_tid, NULL, http_client_thread, NULL, 0);
+#if HTTPCLIENT_TESTSUITE
+ http_client_testsuite_run();
+#endif
+}
+
+void
+http_client_done ( void )
+{
+ http_running = 0;
+ tvh_write(http_pipe.wr, "", 1);
+ pthread_join(http_client_tid, NULL);
+ assert(TAILQ_FIRST(&http_clients) == NULL);
+ tvh_pipe_close(&http_pipe);
+ tvhpoll_destroy(http_poll);
+}
+
+/*
+ *
+ * TESTSUITE
+ *
+ */
+
+#if HTTPCLIENT_TESTSUITE
+
+static int
+http_client_testsuite_hdr_received( http_client_t *hc )
+{
+ http_arg_t *ra;
+
+ fprintf(stderr, "HTTPCTS: Received header from %s:%i\n", hc->hc_host, hc->hc_port);
+ TAILQ_FOREACH(ra, &hc->hc_args, link)
+ fprintf(stderr, " %s: %s\n", ra->key, ra->val);
+ return 0;
+}
+
+static void
+http_client_testsuite_conn_closed( http_client_t *hc, int result )
+{
+ fprintf(stderr, "HTTPCTS: Closed (result=%i - %s)\n", result, strerror(result));
+}
+
+static int
+http_client_testsuite_data_complete( http_client_t *hc )
+{
+ fprintf(stderr, "HTTPCTS: Data Complete (code=%i, data=%p, data_size=%li)\n",
+ hc->hc_code, hc->hc_data, hc->hc_data_size);
+ return 0;
+}
+
+static int
+http_client_testsuite_data_received( http_client_t *hc, void *data, size_t len )
+{
+ fprintf(stderr, "HTTPCTS: Data received (len=%li)\n", len);
+ /* check, if the data memory area is OK */
+ memset(data, 0xa5, len);
+ return 0;
+}
+
+static struct strtab HTTP_contab[] = {
+ { "WAIT_REQUEST", HTTP_CON_WAIT_REQUEST },
+ { "READ_HEADER", HTTP_CON_READ_HEADER },
+ { "END", HTTP_CON_END },
+ { "POST_DATA", HTTP_CON_POST_DATA },
+ { "SENDING", HTTP_CON_SENDING },
+ { "SENT", HTTP_CON_SENT },
+ { "RECEIVING", HTTP_CON_RECEIVING },
+ { "DONE", HTTP_CON_DONE },
+};
+
+static struct strtab ERRNO_tab[] = {
+ { "EPERM", EPERM },
+ { "ENOENT", ENOENT },
+ { "ESRCH", ESRCH },
+ { "EINTR", EINTR },
+ { "EIO", EIO },
+ { "ENXIO", ENXIO },
+ { "E2BIG", E2BIG },
+ { "ENOEXEC", ENOEXEC },
+ { "EBADF", EBADF },
+ { "ECHILD", ECHILD },
+ { "EAGAIN", EAGAIN },
+ { "ENOMEM", ENOMEM },
+ { "EACCES", EACCES },
+ { "EFAULT", EFAULT },
+ { "ENOTBLK", ENOTBLK },
+ { "EBUSY", EBUSY },
+ { "EEXIST", EEXIST },
+ { "EXDEV", EXDEV },
+ { "ENODEV", ENODEV },
+ { "ENOTDIR", ENOTDIR },
+ { "EISDIR", EISDIR },
+ { "EINVAL", EINVAL },
+ { "ENFILE", ENFILE },
+ { "EMFILE", EMFILE },
+ { "ENOTTY", ENOTTY },
+ { "ETXTBSY", ETXTBSY },
+ { "EFBIG", EFBIG },
+ { "ENOSPC", ENOSPC },
+ { "ESPIPE", ESPIPE },
+ { "EROFS", EROFS },
+ { "EMLINK", EMLINK },
+ { "EPIPE", EPIPE },
+ { "EDOM", EDOM },
+ { "ERANGE", ERANGE },
+ { "EDEADLK", EDEADLK },
+ { "ENAMETOOLONG", ENAMETOOLONG },
+ { "ENOLCK", ENOLCK },
+ { "ENOSYS", ENOSYS },
+ { "ENOTEMPTY", ENOTEMPTY },
+ { "ELOOP", ELOOP },
+ { "EWOULDBLOCK", EWOULDBLOCK },
+ { "ENOMSG", ENOMSG },
+ { "EIDRM", EIDRM },
+ { "ECHRNG", ECHRNG },
+ { "EL2NSYNC", EL2NSYNC },
+ { "EL3HLT", EL3HLT },
+ { "EL3RST", EL3RST },
+ { "ELNRNG", ELNRNG },
+ { "EUNATCH", EUNATCH },
+ { "ENOCSI", ENOCSI },
+ { "EL2HLT", EL2HLT },
+ { "EBADE", EBADE },
+ { "EBADR", EBADR },
+ { "EXFULL", EXFULL },
+ { "ENOANO", ENOANO },
+ { "EBADRQC", EBADRQC },
+ { "EBADSLT", EBADSLT },
+ { "EDEADLOCK", EDEADLOCK },
+ { "EBFONT", EBFONT },
+ { "ENOSTR", ENOSTR },
+ { "ENODATA", ENODATA },
+ { "ETIME", ETIME },
+ { "ENOSR", ENOSR },
+ { "ENONET", ENONET },
+ { "ENOPKG", ENOPKG },
+ { "EREMOTE", EREMOTE },
+ { "ENOLINK", ENOLINK },
+ { "EADV", EADV },
+ { "ESRMNT", ESRMNT },
+ { "ECOMM", ECOMM },
+ { "EPROTO", EPROTO },
+ { "EMULTIHOP", EMULTIHOP },
+ { "EDOTDOT", EDOTDOT },
+ { "EBADMSG", EBADMSG },
+ { "EOVERFLOW", EOVERFLOW },
+ { "ENOTUNIQ", ENOTUNIQ },
+ { "EBADFD", EBADFD },
+ { "EREMCHG", EREMCHG },
+ { "ELIBACC", ELIBACC },
+ { "ELIBBAD", ELIBBAD },
+ { "ELIBSCN", ELIBSCN },
+ { "ELIBMAX", ELIBMAX },
+ { "ELIBEXEC", ELIBEXEC },
+ { "EILSEQ", EILSEQ },
+ { "ERESTART", ERESTART },
+ { "ESTRPIPE", ESTRPIPE },
+ { "EUSERS", EUSERS },
+ { "ENOTSOCK", ENOTSOCK },
+ { "EDESTADDRREQ", EDESTADDRREQ },
+ { "EMSGSIZE", EMSGSIZE },
+ { "EPROTOTYPE", EPROTOTYPE },
+ { "ENOPROTOOPT", ENOPROTOOPT },
+ { "EPROTONOSUPPORT", EPROTONOSUPPORT },
+ { "ESOCKTNOSUPPORT", ESOCKTNOSUPPORT },
+ { "EOPNOTSUPP", EOPNOTSUPP },
+ { "EPFNOSUPPORT", EPFNOSUPPORT },
+ { "EAFNOSUPPORT", EAFNOSUPPORT },
+ { "EADDRINUSE", EADDRINUSE },
+ { "EADDRNOTAVAIL", EADDRNOTAVAIL },
+ { "ENETDOWN", ENETDOWN },
+ { "ENETUNREACH", ENETUNREACH },
+ { "ENETRESET", ENETRESET },
+ { "ECONNABORTED", ECONNABORTED },
+ { "ECONNRESET", ECONNRESET },
+ { "ENOBUFS", ENOBUFS },
+ { "EISCONN", EISCONN },
+ { "ENOTCONN", ENOTCONN },
+ { "ESHUTDOWN", ESHUTDOWN },
+ { "ETOOMANYREFS", ETOOMANYREFS },
+ { "ETIMEDOUT", ETIMEDOUT },
+ { "ECONNREFUSED", ECONNREFUSED },
+ { "EHOSTDOWN", EHOSTDOWN },
+ { "EHOSTUNREACH", EHOSTUNREACH },
+ { "EALREADY", EALREADY },
+ { "EINPROGRESS", EINPROGRESS },
+ { "ESTALE", ESTALE },
+ { "EUCLEAN", EUCLEAN },
+ { "ENOTNAM", ENOTNAM },
+ { "ENAVAIL", ENAVAIL },
+ { "EISNAM", EISNAM },
+ { "EREMOTEIO", EREMOTEIO },
+ { "EDQUOT", EDQUOT },
+ { "ENOMEDIUM", ENOMEDIUM },
+ { "EMEDIUMTYPE", EMEDIUMTYPE },
+ { "ECANCELED", ECANCELED },
+ { "ENOKEY", ENOKEY },
+ { "EKEYEXPIRED", EKEYEXPIRED },
+ { "EKEYREVOKED", EKEYREVOKED },
+ { "EKEYREJECTED", EKEYREJECTED },
+ { "EOWNERDEAD", EOWNERDEAD },
+ { "ENOTRECOVERABLE", ENOTRECOVERABLE },
+ { "ERFKILL", ERFKILL },
+ { "EHWPOISON", EHWPOISON },
+};
+
+void
+http_client_testsuite_run( void )
+{
+ const char *path, *cs, *cs2;
+ char line[1024], *s;
+ http_arg_list_t args;
+ http_client_t *hc = NULL;
+ http_cmd_t cmd;
+ http_ver_t ver = HTTP_VERSION_1_1;
+ int data_transfer = 0, port = 0;
+ size_t data_limit = 0;
+ tvhpoll_event_t ev;
+ tvhpoll_t *efd;
+ url_t u1, u2;
+ FILE *fp;
+ int r, expected = HTTP_CON_DONE;
+ int handle_location = 0;
+
+ path = getenv("TVHEADEND_HTTPC_TEST");
+ if (path == NULL)
+ path = TVHEADEND_DATADIR "/support/httpc-test.txt";
+ fp = fopen(path, "r");
+ if (fp == NULL) {
+ tvhlog(LOG_ERR, "httpc", "Test: unable to open '%s': %s", path, strerror(errno));
+ return;
+ }
+ memset(&u1, 0, sizeof(u1));
+ memset(&u2, 0, sizeof(u2));
+ http_arg_init(&args);
+ efd = tvhpoll_create(1);
+ while (fgets(line, sizeof(line), fp) != NULL && tvheadend_running) {
+ if (line[0] == '\0')
+ continue;
+ s = line + strlen(line) - 1;
+ while (*s < ' ' && s != line)
+ s--;
+ if (*s < ' ')
+ *s = '\0';
+ else
+ s[1] = '\0';
+ s = line;
+ while (*s && *s < ' ')
+ s++;
+ if (*s == '\0' || *s == '#')
+ continue;
+ if (strcmp(s, "Reset=1") == 0) {
+ ver = HTTP_VERSION_1_1;
+ urlreset(&u1);
+ urlreset(&u2);
+ http_client_close(hc);
+ hc = NULL;
+ data_transfer = 0;
+ data_limit = 0;
+ port = 0;
+ expected = HTTP_CON_DONE;
+ handle_location = 0;
+ } else if (strcmp(s, "DataTransfer=all") == 0) {
+ data_transfer = 0;
+ } else if (strcmp(s, "DataTransfer=cont") == 0) {
+ data_transfer = 1;
+ } else if (strcmp(s, "HandleLocation=0") == 0) {
+ handle_location = 0;
+ } else if (strcmp(s, "HandleLocation=1") == 0) {
+ handle_location = 1;
+ } else if (strncmp(s, "DataLimit=", 10) == 0) {
+ data_limit = atoll(s + 10);
+ } else if (strncmp(s, "Port=", 5) == 0) {
+ port = atoi(s + 5);
+ } else if (strncmp(s, "ExpectedError=", 14) == 0) {
+ r = str2val(s + 14, HTTP_contab);
+ if (r < 0) {
+ r = str2val(s + 14, ERRNO_tab);
+ if (r < 0) {
+ fprintf(stderr, "HTTPCTS: Unknown error code '%s'\n", s + 14);
+ goto fatal;
+ } else {
+ r = -r;
+ }
+ }
+ expected = r;
+ } else if (strncmp(s, "Header=", 7) == 0) {
+ r = http_client_parse_arg(&args, s + 7);
+ if (r < 0)
+ goto fatal;
+ } else if (strncmp(s, "Version=", 8) == 0) {
+ ver = http_str2ver(s + 8);
+ if (ver < 0)
+ goto fatal;
+ } else if (strncmp(s, "URL=", 4) == 0) {
+ urlreset(&u1);
+ if (urlparse(s + 4, &u1) < 0)
+ goto fatal;
+ } else if (strncmp(s, "Command=", 8) == 0) {
+ if (u1.host == NULL || u1.host[0] == '\0') {
+ fprintf(stderr, "HTTPCTS: Define URL\n");
+ goto fatal;
+ }
+ cmd = http_str2cmd(s + 8);
+ if (cmd < 0)
+ goto fatal;
+ if (http_arg_get(&args, "Host") == NULL && u1.host && u1.host[0] != '\0')
+ http_arg_set(&args, "Host", u1.host);
+ if (u2.host == NULL || u1.host == NULL || strcmp(u1.host, u2.host) ||
+ u2.port != u1.port || !hc->hc_keepalive) {
+ http_client_close(hc);
+ if (port)
+ u1.port = port;
+ hc = http_client_connect(NULL, ver, u1.scheme, u1.host, u1.port);
+ if (hc == NULL) {
+ fprintf(stderr, "HTTPCTS: Unable to connect to %s:%i (%s)\n", u1.host, u1.port, u1.scheme);
+ goto fatal;
+ } else {
+ fprintf(stderr, "HTTPCTS: Connected to %s:%i\n", hc->hc_host, hc->hc_port);
+ }
+ }
+ fprintf(stderr, "HTTPCTS Send: Cmd=%s Ver=%s Host=%s Path=%s\n",
+ http_cmd2str(cmd), http_ver2str(ver), http_arg_get(&args, "Host"), u1.path);
+ hc->hc_efd = efd;
+ hc->hc_handle_location = handle_location;
+ hc->hc_data_limit = data_limit;
+ hc->hc_hdr_received = http_client_testsuite_hdr_received;
+ hc->hc_data_complete = http_client_testsuite_data_complete;
+ hc->hc_conn_closed = http_client_testsuite_conn_closed;
+ if (data_transfer) {
+ hc->hc_data_received = http_client_testsuite_data_received;
+ } else {
+ hc->hc_data_received = NULL;
+ }
+ r = http_client_send(hc, cmd, u1.path, u1.query, &args, NULL, 0);
+ if (r < 0) {
+ fprintf(stderr, "HTTPCTS Send Failed %s\n", strerror(-r));
+ goto fatal;
+ }
+ while (tvheadend_running) {
+ fprintf(stderr, "HTTPCTS: Enter Poll\n");
+ r = tvhpoll_wait(efd, &ev, 1, -1);
+ fprintf(stderr, "HTTPCTS: Leave Poll: %i (%s)\n", r, val2str(r, ERRNO_tab));
+ if (r < 0 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK))
+ continue;
+ if (r < 0) {
+ fprintf(stderr, "HTTPCTS: Poll result: %s\n", strerror(-r));
+ goto fatal;
+ }
+ if (r != 1)
+ continue;
+ if (ev.data.ptr != hc) {
+ fprintf(stderr, "HTTPCTS: Poll returned a wrong value\n");
+ goto fatal;
+ }
+ r = http_client_run(hc);
+ cs = val2str(r, HTTP_contab);
+ if (cs == NULL)
+ cs = val2str(-r, ERRNO_tab);
+ cs2 = val2str(expected, HTTP_contab);
+ if (cs2 == NULL)
+ cs2 = val2str(-expected, ERRNO_tab);
+ fprintf(stderr, "HTTPCTS: Run Done, Result = %i (%s), Expected = %i (%s)\n", r, cs, expected, cs2);
+ if (r == expected)
+ break;
+ if (r < 0)
+ goto fatal;
+ if (r == HTTP_CON_DONE)
+ goto fatal;
+ }
+ urlreset(&u2);
+ urlcopy(&u2, &u1);
+ urlreset(&u1);
+ http_client_clear_state(hc);
+ } else {
+ fprintf(stderr, "HTTPCTS: Wrong line '%s'\n", s);
+ }
+ }
+ urlreset(&u2);
+ urlreset(&u1);
+ http_client_close(hc);
+ tvhpoll_destroy(efd);
+ http_arg_flush(&args);
+ fclose(fp);
+ fprintf(stderr, "HTTPCTS Return To Main\n");
+ return;
+fatal:
+ fprintf(stderr, "HTTPCTS Fatal Error\n");
+ abort();
+}
+
+#endif
diff --git a/src/input/mpegts/iptv/iptv_http.c b/src/input/mpegts/iptv/iptv_http.c
index 746397cc..770dff4a 100644
--- a/src/input/mpegts/iptv/iptv_http.c
+++ b/src/input/mpegts/iptv/iptv_http.c
@@ -21,27 +21,26 @@
#include "iptv_private.h"
#include "http.h"
-#if ENABLE_CURL
-
/*
* Connected
*/
-static void
-iptv_http_conn ( void *p )
+static int
+iptv_http_header ( http_client_t *hc )
{
pthread_mutex_lock(&global_lock);
- iptv_input_mux_started(p);
+ iptv_input_mux_started(hc->hc_aux);
pthread_mutex_unlock(&global_lock);
+ return 0;
}
/*
* Receive data
*/
-static size_t
+static int
iptv_http_data
- ( void *p, void *buf, size_t len )
+ ( http_client_t *hc, void *buf, size_t len )
{
- iptv_mux_t *im = p;
+ iptv_mux_t *im = hc->hc_aux;
pthread_mutex_lock(&iptv_lock);
@@ -52,7 +51,7 @@ iptv_http_data
pthread_mutex_unlock(&iptv_lock);
- return len;
+ return 0;
}
/*
@@ -63,8 +62,19 @@ iptv_http_start
( iptv_mux_t *im, const url_t *u )
{
http_client_t *hc;
- if (!(hc = http_connect(u, iptv_http_conn, iptv_http_data, NULL, im)))
+ int r;
+
+ if (!(hc = http_client_connect(im, HTTP_VERSION_1_1, u->scheme,
+ u->host, u->port)))
return SM_CODE_TUNING_FAILED;
+ hc->hc_hdr_received = iptv_http_header;
+ hc->hc_data_received = iptv_http_data;
+ http_client_register(hc);
+ r = http_client_simple(hc, u);
+ if (r < 0) {
+ http_client_close(hc);
+ return SM_CODE_TUNING_FAILED;
+ }
im->im_data = hc;
return 0;
@@ -77,7 +87,7 @@ static void
iptv_http_stop
( iptv_mux_t *im )
{
- http_close(im->im_data);
+ http_client_close(im->im_data);
}
@@ -102,12 +112,3 @@ iptv_http_init ( void )
};
iptv_handler_register(ih, 2);
}
-
-#else /* ENABLE_CURL */
-
-void
-iptv_http_init ( void )
-{
-}
-
-#endif /* ENABLE_CURL */
diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c
index 8dd439c3..c4798ab4 100644
--- a/src/input/mpegts/satip/satip.c
+++ b/src/input/mpegts/satip/satip.c
@@ -444,7 +444,6 @@ typedef struct satip_discovery {
url_t url;
http_client_t *http_client;
time_t http_start;
- char *desc;
} satip_discovery_t;
TAILQ_HEAD(satip_discovery_queue, satip_discovery);
@@ -465,7 +464,7 @@ satip_discovery_destroy(satip_discovery_t *d, int unlink)
TAILQ_REMOVE(&satip_discoveries, d, disc_link);
}
if (d->http_client)
- http_close(d->http_client);
+ http_client_close(d->http_client);
urlreset(&d->url);
free(d->myaddr);
free(d->location);
@@ -474,7 +473,6 @@ satip_discovery_destroy(satip_discovery_t *d, int unlink)
free(d->bootid);
free(d->configid);
free(d->deviceid);
- free(d->desc);
free(d);
}
@@ -489,11 +487,10 @@ satip_discovery_find(satip_discovery_t *d)
return NULL;
}
-static size_t
-satip_discovery_http_data(void *p, void *buf, size_t len)
+static void
+satip_discovery_http_closed(http_client_t *hc, int errn)
{
- satip_discovery_t *d = p;
- size_t size;
+ satip_discovery_t *d = hc->hc_aux;
char *s;
htsmsg_t *xml = NULL, *tags, *root, *device;
const char *friendlyname, *manufacturer, *manufacturerURL, *modeldesc;
@@ -503,24 +500,27 @@ satip_discovery_http_data(void *p, void *buf, size_t len)
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 = http_arg_get(&hc->hc_args, "Content-Type");
+ if (s && strcasecmp(s, "text/xml")) {
+ errn = EMEDIUMTYPE;
+ s = NULL;
+ }
+ if (errn != 0 || s == NULL || hc->hc_code != 200 ||
+ hc->hc_data_size == 0 || hc->hc_data == NULL) {
+ tvhlog(LOG_ERR, "satip", "Cannot get %s: %s", d->location, strerror(errn));
+ return;
+ }
- s = d->desc + size - 1;
- while (s != d->desc && *s != '/')
+ s = hc->hc_data + hc->hc_data_size - 1;
+ while (s != hc->hc_data && *s != '/')
s--;
- if (s != d->desc)
+ if (s != hc->hc_data)
s--;
if (strncmp(s, "", 7))
- return len;
+ return;
/* Parse */
- xml = htsmsg_xml_deserialize(d->desc, errbuf, sizeof(errbuf));
- d->desc = NULL;
+ xml = htsmsg_xml_deserialize(hc->hc_data, errbuf, sizeof(errbuf));
+ hc->hc_data = NULL;
if (!xml) {
tvhlog(LOG_ERR, "satip_discovery_desc", "htsmsg_xml_deserialize error %s", errbuf);
goto finish;
@@ -599,21 +599,13 @@ satip_discovery_http_data(void *p, void *buf, size_t len)
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;
+ int r;
lock_assert(&global_lock);
@@ -626,14 +618,19 @@ satip_discovery_timerq_cb(void *aux)
satip_discovery_destroy(d, 1);
continue;
}
- d->http_client = http_connect(&d->url, NULL,
- satip_discovery_http_data,
- satip_discovery_http_fail,
- d);
+
+ d->http_client = http_client_connect(d, HTTP_VERSION_1_1, d->url.scheme,
+ d->url.host, d->url.port);
if (d->http_client == NULL)
satip_discovery_destroy(d, 1);
- else
+ else {
d->http_start = dispatch_clock;
+ d->http_client->hc_conn_closed = satip_discovery_http_closed;
+ http_client_register(d->http_client);
+ r = http_client_simple(d->http_client, &d->url);
+ if (r < 0)
+ satip_discovery_destroy(d, 1);
+ }
}
if (TAILQ_FIRST(&satip_discoveries))
gtimer_arm(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 5);
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
index a9f2117a..be473a84 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -73,7 +73,8 @@ satip_rtsp_send_partial( satip_rtsp_connection_t *conn )
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)
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK ||
+ errno == EINPROGRESS)
return SATIP_RTSP_INCOMPLETE;
return -errno;
}
@@ -238,7 +239,7 @@ 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, ',');
diff --git a/src/main.c b/src/main.c
index e8e2ff8d..fba0b10c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -69,6 +69,11 @@
#ifdef PLATFORM_LINUX
#include
#endif
+#include
+#include
+#include
+#include
+#include
pthread_t main_tid;
@@ -730,6 +735,11 @@ main(int argc, char **argv)
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, NULL);
trap_init(argv[0]);
+
+ /* SSL library init */
+ OPENSSL_config(NULL);
+ SSL_load_error_strings();
+ SSL_library_init();
/* Initialise configuration */
uuid_init();
@@ -872,7 +882,21 @@ main(int argc, char **argv)
free(opt_tsfile.str);
- curl_done();
+ /* OpenSSL - welcome to the "cleanup" hell */
+ ENGINE_cleanup();
+ RAND_cleanup();
+ CRYPTO_cleanup_all_ex_data();
+ EVP_cleanup();
+ CONF_modules_free();
+ COMP_zlib_cleanup();
+ ERR_remove_state(0);
+ ERR_free_strings();
+ {
+ struct stack_st_SSL_COMP * pCOMP = SSL_COMP_get_compression_methods();
+ if (pCOMP)
+ sk_SSL_COMP_free(pCOMP);
+ }
+ /* end of OpenSSL cleanup code */
return 0;
}
diff --git a/src/tcp.c b/src/tcp.c
index 272ba787..af60bc10 100644
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -142,7 +142,9 @@ tcp_connect(const char *hostname, int port, char *errbuf, size_t errbufsize,
free(tmphstbuf);
if(r == -1) {
- if(errno == EINPROGRESS) {
+ if(errno == EINPROGRESS && timeout < 0) {
+ err = 0;
+ } else if(errno == EINPROGRESS) {
struct pollfd pfd;
pfd.fd = fd;
diff --git a/src/tvheadend.h b/src/tvheadend.h
index 15031f45..f0601ab1 100644
--- a/src/tvheadend.h
+++ b/src/tvheadend.h
@@ -575,6 +575,11 @@ uint32_t tvh_crc32(const uint8_t *data, size_t datalen, uint32_t crc);
int base64_decode(uint8_t *out, const char *in, int out_size);
+char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size);
+
+/* Calculate the output size needed to base64-encode x bytes. */
+#define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1)
+
int put_utf8(char *out, int c);
static inline int64_t ts_rescale(int64_t ts, int tb)
diff --git a/src/utils.c b/src/utils.c
index 7861b57a..6278facc 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -193,6 +193,41 @@ base64_decode(uint8_t *out, const char *in, int out_size)
return dst - out;
}
+/*
+ * b64_encode: Stolen from VLC's http.c.
+ * Simplified by Michael.
+ * Fixed edge cases and made it work from data (vs. strings) by Ryan.
+ */
+
+char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size)
+{
+ static const char b64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ char *ret, *dst;
+ unsigned i_bits = 0;
+ int i_shift = 0;
+ int bytes_remaining = in_size;
+
+ if (in_size >= UINT_MAX / 4 ||
+ out_size < BASE64_SIZE(in_size))
+ return NULL;
+ ret = dst = out;
+ while (bytes_remaining) {
+ i_bits = (i_bits << 8) + *in++;
+ bytes_remaining--;
+ i_shift += 8;
+
+ do {
+ *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f];
+ i_shift -= 6;
+ } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0));
+ }
+ while ((dst - ret) & 3)
+ *dst++ = '=';
+ *dst = '\0';
+
+ return ret;
+}
/**
*
diff --git a/support/httpc-test.txt b/support/httpc-test.txt
new file mode 100644
index 00000000..26bb6e9b
--- /dev/null
+++ b/support/httpc-test.txt
@@ -0,0 +1,81 @@
+#
+# File format:
+# Header= pass this in the http request
+# URL= URL
+# HandleLocation= Handle the location (redirection) requests (0 or 1)
+# DataTransfer=all grab all data and pass then in the data_complete callback
+# DataTransfer=cont continuous passing data to the data_received callback
+# DataLimit= limit data to these bytes
+# ExpectedError= expected error
+# Reset=1 reset the initial state - close the current keep-alive conn
+#
+
+Header=Connection: close
+URL=http://www.google.com
+DataTransfer=all
+HandleLocation=1
+Command=GET
+
+Header=Connection: close
+URL=http://www.google.com
+DataTransfer=all
+DataLimit=10
+ExpectedError=EOVERFLOW
+HandleLocation=1
+Command=GET
+
+#
+# Keep-alive connection test
+#
+
+Reset=1
+
+DataTransfer=all
+URL=http://httpbin.org
+Command=GET
+URL=http://httpbin.org
+Command=GET
+URL=http://httpbin.org
+Command=GET
+HandleLocation=1
+URL=http://httpbin.org/relative-redirect/20
+ExpectedError=ELOOP
+Command=GET
+
+#
+# Keep-alive SSL test
+#
+
+Reset=1
+
+URL=https://httpbin.org
+Command=GET
+URL=https://httpbin.org
+Command=GET
+URL=https://httpbin.org
+Command=GET
+HandleLocation=1
+URL=https://httpbin.org/relative-redirect/20
+ExpectedError=ELOOP
+Command=GET
+
+#
+# Google SSL test
+#
+
+Reset=1
+
+Header=Connection: close
+URL=https://www.google.com
+DataTransfer=all
+HandleLocation=1
+Command=GET
+
+Header=Connection: close
+URL=https://www.google.com
+DataTransfer=all
+DataLimit=10
+HandleLocation=1
+ExpectedError=EOVERFLOW
+Command=GET
+
From a57f86f07b5900a815ad2c245d45e8070c0d611e Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Tue, 15 Apr 2014 17:30:47 +0200
Subject: [PATCH 12/42] Move RTSP common code from SAT>IP to the common place
---
Makefile | 1 +
src/http.h | 60 +++-
src/httpc.c | 12 +-
src/input/mpegts/satip/satip_frontend.c | 106 +++----
src/input/mpegts/satip/satip_private.h | 89 +-----
src/input/mpegts/satip/satip_rtsp.c | 391 ++----------------------
src/rtsp.c | 199 ++++++++++++
7 files changed, 337 insertions(+), 521 deletions(-)
create mode 100644 src/rtsp.c
diff --git a/Makefile b/Makefile
index eaa63ad5..6ec18b03 100644
--- a/Makefile
+++ b/Makefile
@@ -114,6 +114,7 @@ SRCS = src/version.c \
src/service_mapper.c \
src/input.c \
src/httpc.c \
+ src/rtsp.c \
src/fsmonitor.c \
src/cron.c \
diff --git a/src/http.h b/src/http.h
index 6abe0ea6..08ab0c3d 100644
--- a/src/http.h
+++ b/src/http.h
@@ -84,7 +84,8 @@ typedef enum http_state {
HTTP_CON_SENT,
HTTP_CON_RECEIVING,
HTTP_CON_DONE,
- HTTP_CON_IDLE
+ HTTP_CON_IDLE,
+ HTTP_CON_OK
} http_state_t;
typedef enum http_cmd {
@@ -265,6 +266,13 @@ struct http_client {
int hc_cseq; /* RTSP */
int hc_rcseq; /* RTSP - expected cseq */
+ char *hc_rtsp_session;
+ char *hc_rtp_dest;
+ int hc_rtp_port;
+ int hc_rtpc_port;
+ int hc_rtp_multicast:1;
+ long hc_rtsp_stream_id;
+ int hc_rtp_timeout;
struct http_client_ssl *hc_ssl; /* ssl internals */
@@ -284,15 +292,45 @@ http_client_connect ( void *aux, http_ver_t ver,
void http_client_register ( http_client_t *hc );
void http_client_close ( http_client_t *hc );
-int
-http_client_send( http_client_t *hc, http_cmd_t cmd,
- const char *path, const char *query,
- http_arg_list_t *header, void *body, size_t body_size );
-int
-http_client_simple( http_client_t *hc, const url_t *url);
-int
-http_client_clear_state( http_client_t *hc );
-int
-http_client_run( http_client_t *hc );
+int http_client_send( http_client_t *hc, http_cmd_t cmd,
+ const char *path, const char *query,
+ http_arg_list_t *header, void *body, size_t body_size );
+int http_client_simple( http_client_t *hc, const url_t *url);
+int http_client_clear_state( http_client_t *hc );
+int http_client_run( http_client_t *hc );
+
+/*
+ * RTSP helpers
+ */
+
+int rtsp_send( http_client_t *hc, http_cmd_t cmd, const char *path,
+ const char *query, http_arg_list_t *hdr );
+
+void rtsp_clear_session( http_client_t *hc );
+
+int rtsp_options_decode( http_client_t *hc );
+static inline int rtsp_options( http_client_t *hc ) {
+ return rtsp_send(hc, RTSP_CMD_OPTIONS, NULL, NULL, NULL);
+}
+
+int rtsp_setup_decode( http_client_t *hc, int satip );
+int rtsp_setup( http_client_t *hc, const char *path, const char *query,
+ const char *multicast_addr, int rtp_port, int rtpc_port );
+
+static inline int
+rtsp_play( http_client_t *hc, const char *path, const char *query ) {
+ return rtsp_send(hc, RTSP_CMD_PLAY, path, query, NULL);
+}
+
+static inline int
+rtsp_teardown( http_client_t *hc, const char *path, const char *query ) {
+ return rtsp_send(hc, RTSP_CMD_TEARDOWN, path, query, NULL);
+}
+
+int rtsp_describe_decode( http_client_t *hc );
+static inline int
+rtsp_describe( http_client_t *hc, const char *path, const char *query ) {
+ return rtsp_send(hc, RTSP_CMD_DESCRIBE, path, query, NULL);
+}
#endif /* HTTP_H_ */
diff --git a/src/httpc.c b/src/httpc.c
index 5e3a523c..c9c845b6 100644
--- a/src/httpc.c
+++ b/src/httpc.c
@@ -581,7 +581,8 @@ http_client_finish( http_client_t *hc )
return http_client_flush(hc, res);
}
hc->hc_hsize = hc->hc_csize = 0;
- if (hc->hc_handle_location &&
+ if (hc->hc_version != RTSP_VERSION_1_0 &&
+ hc->hc_handle_location &&
(hc->hc_code == HTTP_STATUS_MOVED ||
hc->hc_code == HTTP_STATUS_FOUND ||
hc->hc_code == HTTP_STATUS_SEE_OTHER ||
@@ -927,7 +928,12 @@ header:
goto next_header;
}
hc->hc_rpos = 0;
- hc->hc_in_data = 1;
+ if (hc->hc_version == RTSP_VERSION_1_0 && !hc->hc_csize) {
+ hc->hc_csize = -1;
+ hc->hc_in_data = 0;
+ } else {
+ hc->hc_in_data = 1;
+ }
res = http_client_data_received(hc, hc->hc_rbuf + hc->hc_hsize, len);
if (res < 0)
return http_client_flush(hc, res);
@@ -1168,6 +1174,7 @@ http_client_connect
hc = calloc(1, sizeof(http_client_t));
hc->hc_aux = aux;
hc->hc_io_size = 1024;
+ hc->hc_rtsp_stream_id = -1;
TAILQ_INIT(&hc->hc_args);
TAILQ_INIT(&hc->hc_wqueue);
@@ -1216,6 +1223,7 @@ http_client_close ( http_client_t *hc )
while ((wcmd = TAILQ_FIRST(&hc->hc_wqueue)) != NULL)
http_client_cmd_destroy(hc, wcmd);
http_client_ssl_free(hc);
+ rtsp_clear_session(hc);
free(hc->hc_location);
free(hc->hc_rbuf);
free(hc->hc_data);
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 7d24de86..859e556c 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -525,12 +525,13 @@ satip_frontend_store_pids(char *buf, uint16_t *pids, int count)
}
static void
-satip_frontend_pid_changed( satip_rtsp_connection_t *rtsp,
+satip_frontend_pid_changed( http_client_t *rtsp,
satip_frontend_t *lfe, const char *name )
{
char *add, *del;
int i, j, r, count, any = lfe->sf_pids_any;
int deleted;
+ int max_pids_len = lfe->sf_device->sd_pids_len;
if (!lfe->sf_running)
return;
@@ -552,7 +553,7 @@ satip_frontend_pid_changed( satip_rtsp_connection_t *rtsp,
lfe->sf_pids_tcount = lfe->sf_pids_count;
pthread_mutex_unlock(&lfe->sf_dvr_lock);
- r = satip_rtsp_play(rtsp, "all", NULL, NULL);
+ r = satip_rtsp_play(rtsp, "all", NULL, NULL, max_pids_len);
} else if (!lfe->sf_device->sd_pids_deladd ||
lfe->sf_pids_any_tuned ||
@@ -572,7 +573,7 @@ satip_frontend_pid_changed( satip_rtsp_connection_t *rtsp,
lfe->sf_pids_tcount = lfe->sf_pids_count;
pthread_mutex_unlock(&lfe->sf_dvr_lock);
- r = satip_rtsp_play(rtsp, add, NULL, NULL);
+ r = satip_rtsp_play(rtsp, add, NULL, NULL, max_pids_len);
} else {
@@ -638,7 +639,7 @@ satip_frontend_pid_changed( satip_rtsp_connection_t *rtsp,
pthread_mutex_unlock(&lfe->sf_dvr_lock);
if (add[0] != '\0' || del[0] != '\0')
- r = satip_rtsp_play(rtsp, NULL, add, del);
+ r = satip_rtsp_play(rtsp, NULL, add, del, max_pids_len);
else
r = 0;
}
@@ -651,9 +652,10 @@ static void *
satip_frontend_input_thread ( void *aux )
{
#define PKTS 64
+#define HTTP_CMD_NONE 9874
satip_frontend_t *lfe = aux;
mpegts_mux_instance_t *mmi = lfe->sf_mmi;
- satip_rtsp_connection_t *rtsp;
+ http_client_t *rtsp;
dvb_mux_t *lm;
char buf[256];
uint8_t tsb[PKTS][1356 + 128];
@@ -666,7 +668,6 @@ satip_frontend_input_thread ( void *aux )
size_t c;
int tc;
tvhpoll_event_t ev[4];
- tvhpoll_event_t evr;
tvhpoll_t *efd;
int changing = 0, ms = -1, fatal = 0;
uint32_t seq = -1, nseq;
@@ -678,7 +679,8 @@ satip_frontend_input_thread ( void *aux )
lm = (dvb_mux_t *)mmi->mmi_mux;
- rtsp = satip_rtsp_connection(lfe->sf_device);
+ rtsp = http_client_connect(lfe, RTSP_VERSION_1_0, "rstp",
+ lfe->sf_device->sd_info.addr, 554);
if (rtsp == NULL)
return NULL;
@@ -692,13 +694,13 @@ satip_frontend_input_thread ( void *aux )
ev[1].fd = lfe->sf_rtcp->fd;
ev[1].data.ptr = lfe->sf_rtcp;
ev[2].events = TVHPOLL_IN;
- ev[2].fd = rtsp->fd;
+ ev[2].fd = rtsp->hc_fd;
ev[2].data.ptr = rtsp;
- evr = ev[2];
ev[3].events = TVHPOLL_IN;
ev[3].fd = lfe->sf_dvr_pipe.rd;
ev[3].data.ptr = NULL;
tvhpoll_add(efd, ev, 4);
+ rtsp->hc_efd = efd;
/* Read */
memset(&msg, 0, sizeof(msg));
@@ -709,10 +711,10 @@ satip_frontend_input_thread ( void *aux )
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);
+ lfe->sf_rtp_port, &lm->lm_tuning);
if (r < 0) {
tvherror("satip", "%s - failed to tune", buf);
return NULL;
@@ -722,18 +724,6 @@ satip_frontend_input_thread ( void *aux )
while (tvheadend_running && !fatal) {
- if (rtsp->sending) {
- if ((evr.events & TVHPOLL_OUT) == 0) {
- evr.events |= TVHPOLL_OUT;
- tvhpoll_add(efd, &evr, 1);
- }
- } else {
- if (evr.events & TVHPOLL_OUT) {
- evr.events &= ~TVHPOLL_OUT;
- tvhpoll_add(efd, &evr, 1);
- }
- }
-
nfds = tvhpoll_wait(efd, ev, 1, ms);
if (nfds > 0 && ev[0].data.ptr == NULL) {
@@ -747,7 +737,7 @@ satip_frontend_input_thread ( void *aux )
break;
}
- if (changing && rtsp->cmd == SATIP_RTSP_CMD_NONE) {
+ if (changing && rtsp->hc_cmd == HTTP_CMD_NONE) {
ms = -1;
changing = 0;
satip_frontend_pid_changed(rtsp, lfe, buf);
@@ -757,31 +747,32 @@ satip_frontend_input_thread ( void *aux )
if (nfds < 1) continue;
if (ev[0].data.ptr == rtsp) {
- r = satip_rtsp_run(rtsp);
+ r = http_client_run(rtsp);
if (r < 0) {
tvhlog(LOG_ERR, "satip", "%s - RTSP error %d (%s) [%i-%i]",
- buf, r, strerror(-r), rtsp->cmd, rtsp->code);
+ buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_code);
fatal = 1;
- } else if (r == SATIP_RTSP_READ_DONE) {
- switch (rtsp->cmd) {
- case SATIP_RTSP_CMD_OPTIONS:
- r = satip_rtsp_options_decode(rtsp);
+ } else if (r == HTTP_CON_DONE) {
+ switch (rtsp->hc_cmd) {
+ case RTSP_CMD_OPTIONS:
+ r = 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);
+ buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_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) {
+ case RTSP_CMD_SETUP:
+ r = rtsp_setup_decode(rtsp, 1);
+ if (r < 0 || rtsp->hc_rtp_port != lfe->sf_rtp_port ||
+ rtsp->hc_rtpc_port != lfe->sf_rtp_port + 1) {
tvhlog(LOG_ERR, "satip", "%s - RTSP SETUP error %d (%s) [%i-%i]",
- buf, r, strerror(-r), rtsp->cmd, rtsp->code);
+ buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_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);
+ rtsp->hc_host, lfe->sf_number,
+ rtsp->hc_rtsp_session, rtsp->hc_rtsp_stream_id);
pthread_mutex_lock(&global_lock);
satip_frontend_default_tables(lfe, mmi->mmi_mux);
pthread_mutex_unlock(&global_lock);
@@ -789,21 +780,21 @@ satip_frontend_input_thread ( void *aux )
}
break;
default:
- if (rtsp->code >= 400) {
+ if (rtsp->hc_code >= 400) {
tvhlog(LOG_ERR, "satip", "%s - RTSP cmd error %d (%s) [%i-%i]",
- buf, r, strerror(-r), rtsp->cmd, rtsp->code);
+ buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_code);
fatal = 1;
}
break;
}
- rtsp->cmd = SATIP_RTSP_CMD_NONE;
+ rtsp->hc_cmd = HTTP_CMD_NONE;
}
}
/* We need to keep the session alive */
- if (rtsp->ping_time + rtsp->timeout / 2 < dispatch_clock &&
- rtsp->cmd == SATIP_RTSP_CMD_NONE)
- satip_rtsp_options(rtsp);
+ if (rtsp->hc_ping_time + rtsp->hc_rtp_timeout / 2 < dispatch_clock &&
+ rtsp->hc_cmd == HTTP_CMD_NONE)
+ rtsp_options(rtsp);
if (ev[0].data.ptr == lfe->sf_rtcp) {
c = recv(lfe->sf_rtcp->fd, rtcp, sizeof(rtcp), MSG_DONTWAIT);
@@ -875,29 +866,26 @@ satip_frontend_input_thread ( void *aux )
ev[2].data.ptr = NULL;
tvhpoll_rem(efd, ev, 3);
- if (rtsp->stream_id) {
- r = satip_rtsp_teardown(rtsp);
+ if (rtsp->hc_rtsp_stream_id >= 0) {
+ snprintf((char *)rtcp, sizeof(rtcp), "/stream=%li", rtsp->hc_rtsp_stream_id);
+ r = rtsp_teardown(rtsp, (char *)rtcp, NULL);
if (r < 0) {
tvhtrace("satip", "%s - bad teardown", buf);
} else {
- if (r == SATIP_RTSP_INCOMPLETE) {
- evr.events |= TVHPOLL_OUT;
- tvhpoll_add(efd, &evr, 1);
- }
- r = 0;
- while (r == SATIP_RTSP_INCOMPLETE) {
- if (!rtsp->sending) {
- evr.events &= ~TVHPOLL_OUT;
- tvhpoll_add(efd, &evr, 1);
- }
- nfds = tvhpoll_wait(efd, ev, 1, -1);
- if (nfds < 0)
+ while (1) {
+ r = http_client_run(rtsp);
+ if (r != HTTP_CON_RECEIVING && r != HTTP_CON_SENDING)
break;
- r = satip_rtsp_run(rtsp);
+ nfds = tvhpoll_wait(efd, ev, 1, -1);
+ if (nfds <= 0) {
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
+ continue;
+ break;
+ }
}
}
}
- satip_rtsp_connection_close(rtsp);
+ http_client_close(rtsp);
tvhpoll_destroy(efd);
return NULL;
diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h
index 67554e5b..7b096b52 100644
--- a/src/input/mpegts/satip/satip_private.h
+++ b/src/input/mpegts/satip/satip_private.h
@@ -23,6 +23,7 @@
#include "input.h"
#include "htsbuf.h"
#include "udp.h"
+#include "http.h"
#include "satip.h"
#define SATIP_BUF_SIZE (4000*188)
@@ -201,92 +202,14 @@ int satip_satconf_get_position
* 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;
-
-#define SATIP_RTSP_OK 1
-#define SATIP_RTSP_READ_DONE 1
-#define SATIP_RTSP_SEND_DONE 1
-#define SATIP_RTSP_INCOMPLETE 0
-
-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;
- size_t hsize; /* header size */
- size_t csize; /* contents size (exclude header) */
- 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_run( 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,
+satip_rtsp_setup( http_client_t *hc,
int src, int fe, int udp_port,
- const dvb_mux_conf_t *dmc,
- int connection_close );
+ const dvb_mux_conf_t *dmc );
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 );
-
-int
-satip_rtsp_describe_decode( satip_rtsp_connection_t *conn );
-
-int
-satip_rtsp_describe( satip_rtsp_connection_t *conn );
+satip_rtsp_play( http_client_t *hc, const char *pids,
+ const char *addpids, const char *delpids,
+ int max_pids_len );
#endif /* __TVH_SATIP_PRIVATE_H__ */
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
index be473a84..d5bc0d45 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -28,291 +28,6 @@
/*
*
*/
-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 ||
- errno == EINPROGRESS)
- return SATIP_RTSP_INCOMPLETE;
- return -errno;
- }
- conn->wpos += r;
- if (conn->wpos >= conn->wsize) {
- conn->sending = 0;
- return SATIP_RTSP_SEND_DONE;
- }
- break;
- }
- return SATIP_RTSP_INCOMPLETE;
-}
-
-int
-satip_rtsp_send( satip_rtsp_connection_t *conn, htsbuf_queue_t *q,
- satip_rtsp_cmd_t cmd )
-{
- 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
- return satip_rtsp_send_partial(conn);
-}
-
-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 SATIP_RTSP_SEND_DONE;
-}
-
-static char *
-satip_rtsp_hstrip(char *h)
-{
- while (*h && *h <= ' ')
- h++;
- return h;
-}
-
-int
-satip_rtsp_run( satip_rtsp_connection_t *conn )
-{
- char buf[1024], *saveptr, *argv[3], *d, *p, *p1;
- htsbuf_queue_t header;
- int cseq_seen;
- ssize_t r;
- size_t len;
-
- if (conn->sending) {
- r = satip_rtsp_send_partial(conn);
- if (r < 0 || r == SATIP_RTSP_INCOMPLETE)
- return 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 SATIP_RTSP_INCOMPLETE;
- 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->csize && conn->rsize > 3 &&
- (d = strstr(conn->rbuf, "\r\n\r\n")) != NULL) {
- conn->hsize = d - conn->rbuf + 4;
- *d = '\0';
- htsbuf_queue_init(&header, 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) {
- if (strncasecmp(p, "CSeq:", 5) == 0) {
- p1 = satip_rtsp_hstrip(p + 5);
- if (p1)
- cseq_seen = conn->cseq == atoi(p1);
- } else if (strncasecmp(p, "Content-Length:", 15) == 0) {
- conn->csize = atoll(p + 15);
- } else {
- htsbuf_append(&header, p, strlen(p));
- htsbuf_append(&header, "\n", 1);
- }
- }
- if (!cseq_seen)
- goto fail;
- free(conn->header);
- conn->header = htsbuf_to_string(&header);
- htsbuf_queue_flush(&header);
- free(conn->data);
- conn->data = NULL;
- if (!conn->csize)
- goto processed;
- if (conn->rsize > conn->hsize)
- goto data;
- } else if (conn->hsize + conn->csize >= conn->rsize) {
-data:
- conn->data = malloc(conn->csize + 1);
- memcpy(conn->data, conn->rbuf + conn->hsize, conn->csize);
- conn->data[conn->csize] = '\0';
-processed:
- len = conn->hsize + conn->csize;
- memcpy(conn->rbuf, conn->rbuf + len, conn->rsize - len);
- conn->rsize -= len;
-#if ENABLE_TRACE
- tvhtrace("satip", "%s - received RTSP header", conn->device->sd_info.addr);
- tvhlog_hexdump("satip", conn->header, strlen(conn->header));
- if (conn->csize) {
- tvhtrace("satip", "%s - received RTSP data", conn->device->sd_info.addr);
- tvhlog_hexdump("satip", conn->data, conn->csize);
- }
-#endif
- conn->hsize = conn->csize = 0;
- /* second write */
- if (conn->wq2_loaded && conn->code == 200 && !conn->rsize) {
- r = satip_rtsp_send(conn, &conn->wq2, conn->wq2_cmd);
- htsbuf_queue_flush(&conn->wq2);
- conn->wq2_loaded = 0;
- return r;
- }
- return SATIP_RTSP_READ_DONE;
-fail:
- htsbuf_queue_flush(&header);
- conn->rsize = 0;
- return -EINVAL;
- }
- return SATIP_RTSP_INCOMPLETE;
-}
-
-/*
- *
- */
-
-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 (strcasecmp(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) ? -EIO : SATIP_RTSP_OK;
-}
-
-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 != 200)
- return -EIO;
- conn->client_port = 0;
- s = strtok_r(conn->header, "\n", &saveptr);
- while (s) {
- n = http_tokenize(s, argv, 32, ';');
- if (strcasecmp(argv[0], "Session:") == 0) {
- conn->session = strdup(argv[1]);
- for (i = 2; i < n; i++) {
- if (strncasecmp(argv[i], "timeout=", 8) == 0) {
- conn->timeout = atoi(argv[i] + 8);
- if (conn->timeout <= 20 || conn->timeout > 3600)
- return -EIO;
- }
- }
- } else if (strcasecmp(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 -EIO;
- } else if (strcasecmp(argv[0], "Transport:") == 0) {
- if (strcasecmp(argv[1], "RTP/AVP"))
- return -EIO;
- if (strcasecmp(argv[2], "unicast"))
- return -EIO;
- 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 SATIP_RTSP_OK;
-}
typedef struct tvh2satip {
int t; ///< TVH internal value
@@ -354,9 +69,8 @@ satip_rtsp_add_val(const char *name, char *buf, uint32_t val)
}
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 )
+satip_rtsp_setup( http_client_t *hc, int src, int fe,
+ int udp_port, const dvb_mux_conf_t *dmc )
{
static tvh2satip_t msys[] = {
{ .t = DVB_SYS_DVBT, "dvbt" },
@@ -432,10 +146,9 @@ satip_rtsp_setup( satip_rtsp_connection_t *conn, int src, int fe,
};
char buf[512];
- htsbuf_queue_t q;
- int r;
+ char *stream = NULL;
+ char _stream[32];
- htsbuf_queue_init(&q, 0);
if (src > 0)
sprintf(buf, "src=%i&", src);
else
@@ -469,27 +182,15 @@ satip_rtsp_setup( satip_rtsp_connection_t *conn, int src, int fe,
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;
+ if (hc->hc_rtsp_stream_id >= 0)
+ snprintf(stream = _stream, sizeof(_stream), "/stream=%li",
+ hc->hc_rtsp_stream_id);
+ return rtsp_setup(hc, stream, buf, NULL, udp_port, udp_port + 1);
}
static const char *
-satip_rtsp_pids_strip( satip_rtsp_connection_t *conn, const char *s )
+satip_rtsp_pids_strip( const char *s, int maxlen )
{
- int maxlen = conn->device->sd_pids_len;
char *ptr;
if (s == NULL)
@@ -508,15 +209,19 @@ satip_rtsp_pids_strip( satip_rtsp_connection_t *conn, const char *s )
}
int
-satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids,
- const char *addpids, const char *delpids )
+satip_rtsp_play( http_client_t *hc, const char *pids,
+ const char *addpids, const char *delpids,
+ int max_pids_len )
{
htsbuf_queue_t q;
+ char *stream = NULL;
+ char _stream[32];
+ char *query;
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);
+ pids = satip_rtsp_pids_strip(pids , max_pids_len);
+ addpids = satip_rtsp_pids_strip(addpids, max_pids_len);
+ delpids = satip_rtsp_pids_strip(delpids, max_pids_len);
if (pids == NULL && addpids == NULL && delpids == NULL)
return -EINVAL;
@@ -524,8 +229,6 @@ satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids,
//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);
@@ -535,7 +238,7 @@ satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids,
if (addpids) {
if (delpids) {
/* try to maintain the maximum request size - simple split */
- if (strlen(addpids) + strlen(delpids) <= conn->device->sd_pids_len)
+ if (strlen(addpids) + strlen(delpids) <= max_pids_len)
split = 1;
else
htsbuf_append(&q, "&", 1);
@@ -544,55 +247,11 @@ satip_rtsp_play( satip_rtsp_connection_t *conn, const char *pids,
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 < 0 || !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;
-}
-
-int
-satip_rtsp_describe_decode( satip_rtsp_connection_t *conn )
-{
- printf("describe: %i\n", conn->code);
- printf("header:\n%s\n", conn->header);
- printf("data:\n%s\n", conn->data);
- return SATIP_RTSP_SEND_DONE;
-}
-
-int
-satip_rtsp_describe( satip_rtsp_connection_t *conn )
-{
- int r;
-
- htsbuf_queue_t q;
- htsbuf_queue_init(&q, 0);
- htsbuf_qprintf(&q,
- "DESCRIBE rtsp://%s/ RTSP/1.0\r\n", conn->device->sd_info.addr);
- r = satip_rtsp_send(conn, &q, SATIP_RTSP_CMD_DESCRIBE);
- htsbuf_queue_flush(&q);
+ if (hc->hc_rtsp_stream_id >= 0)
+ snprintf(stream = _stream, sizeof(_stream), "/stream=%li",
+ hc->hc_rtsp_stream_id);
+ query = htsbuf_to_string(&q);
+ r = rtsp_play(hc, stream, query);
+ free(query);
return r;
}
diff --git a/src/rtsp.c b/src/rtsp.c
new file mode 100644
index 00000000..339dc0fd
--- /dev/null
+++ b/src/rtsp.c
@@ -0,0 +1,199 @@
+/*
+ * Tvheadend - RTSP routines
+ *
+ * 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"
+
+/*
+ * Utils
+ */
+int
+rtsp_send( http_client_t *hc, http_cmd_t cmd,
+ const char *path, const char *query,
+ http_arg_list_t *hdr )
+{
+ http_arg_list_t h;
+ size_t blen = 7 + strlen(hc->hc_host) + (path ? strlen(path) : 1) + 1;
+ char *buf = alloca(blen);
+
+ if (hc->hc_rtsp_session && cmd != RTSP_CMD_OPTIONS) {
+ if (hdr == NULL) {
+ hdr = &h;
+ http_arg_init(&h);
+ }
+ http_arg_set(hdr, "Session", hc->hc_rtsp_session);
+ }
+ snprintf(buf, blen, "rtsp://%s%s", hc->hc_host, path ? path : "/");
+ return http_client_send(hc, cmd, buf, query, hdr, NULL, 0);
+}
+
+void
+rtsp_clear_session( http_client_t *hc )
+{
+ free(hc->hc_rtsp_session);
+ free(hc->hc_rtp_dest);
+ hc->hc_rtp_port = 0;
+ hc->hc_rtpc_port = 0;
+ hc->hc_rtsp_session = NULL;
+ hc->hc_rtp_dest = NULL;
+ hc->hc_rtp_multicast = 0;
+ hc->hc_rtsp_stream_id = -1;
+ hc->hc_rtp_timeout = 60;
+}
+
+/*
+ * Options
+ */
+
+int
+rtsp_options_decode( http_client_t *hc )
+{
+ char *argv[32], *p;
+ int i, n, what = 0;
+
+ p = http_arg_get(&hc->hc_args, "Public");
+ n = http_tokenize(p, argv, 32, ',');
+ for (i = 1; i < n; i++) {
+ if (strcmp(argv[i], "DESCRIBE") == 0)
+ what |= 1;
+ else if (strcmp(argv[i], "SETUP") == 0)
+ what |= 2;
+ else if (strcmp(argv[i], "PLAY") == 0)
+ what |= 4;
+ else if (strcmp(argv[i], "TEARDOWN") == 0)
+ what |= 8;
+ }
+ return (hc->hc_code != 200 && what != 0x0f) ? -EIO : HTTP_CON_OK;
+}
+
+int
+rtsp_setup_decode( http_client_t *hc, int satip )
+{
+ char *argv[32], *argv2[2], *p;
+ int i, n, j;
+
+#if 0
+ { http_arg_t *ra;
+ TAILQ_FOREACH(ra, &hc->hc_args, link)
+ printf(" %s: %s\n", ra->key, ra->val); }
+#endif
+ rtsp_clear_session(hc);
+ if (hc->hc_code != 200)
+ return -EIO;
+ p = http_arg_get(&hc->hc_args, "Session");
+ if (p == NULL)
+ return -EIO;
+ n = http_tokenize(p, argv, 32, ';');
+ if (n < 1)
+ return -EIO;
+ hc->hc_rtsp_session = strdup(argv[0]);
+ for (i = 1; i < n; i++) {
+ if (strncasecmp(argv[i], "timeout=", 8) == 0) {
+ hc->hc_rtp_timeout = atoi(argv[i] + 8);
+ if (hc->hc_rtp_timeout <= 20 || hc->hc_rtp_timeout > 3600)
+ return -EIO;
+ }
+ }
+ if (satip) {
+ p = http_arg_get(&hc->hc_args, "com.ses.streamID");
+ if (p == NULL)
+ return -EIO;
+ /* zero is valid stream id per specification */
+ while (*p && (*p == '0' || *p < ' '))
+ p++;
+ if (p[0] == '0' && p[1] == '\0') {
+ hc->hc_rtsp_stream_id = 0;
+ } else {
+ hc->hc_rtsp_stream_id = atoll(p);
+ if (hc->hc_rtsp_stream_id <= 0)
+ return -EIO;
+ }
+ }
+ p = http_arg_get(&hc->hc_args, "Transport");
+ if (p == NULL)
+ return -EIO;
+ n = http_tokenize(p, argv, 32, ';');
+ if (n < 3)
+ return -EIO;
+ if (strcasecmp(argv[0], "RTP/AVP"))
+ return -EIO;
+ hc->hc_rtp_multicast = strcasecmp(argv[1], "multicast") == 0;
+ if (strcasecmp(argv[1], "unicast") && !hc->hc_rtp_multicast)
+ return -EIO;
+ for (i = 2; i < n; i++) {
+ if (strncmp(argv[i], "destination=", 12) == 0)
+ hc->hc_rtp_dest = strdup(argv[i] + 12);
+ else if (strncmp(argv[i], "client_port=", 12) == 0) {
+ j = http_tokenize(argv[i] + 12, argv2, 2, '-');
+ if (j > 0) {
+ hc->hc_rtp_port = atoi(argv2[0]);
+ if (hc->hc_rtp_port <= 0)
+ return -EIO;
+ if (j > 1) {
+ hc->hc_rtpc_port = atoi(argv2[1]);
+ if (hc->hc_rtpc_port <= 0)
+ return -EIO;
+ }
+ } else {
+ return -EIO;
+ }
+ }
+ }
+ return HTTP_CON_OK;
+}
+
+int
+rtsp_setup( http_client_t *hc,
+ const char *path, const char *query,
+ const char *multicast_addr,
+ int rtp_port, int rtpc_port )
+{
+ http_arg_list_t h;
+ char transport[256];
+
+ if (multicast_addr) {
+ snprintf(transport, sizeof(transport),
+ "RTP/AVP;multicast;destination=%s;ttl=1;client_port=%i-%i",
+ multicast_addr, rtp_port, rtpc_port);
+ } else {
+ snprintf(transport, sizeof(transport),
+ "RTP/AVP;unicast;client_port=%i-%i", rtp_port, rtpc_port);
+ }
+
+ http_arg_init(&h);
+ http_arg_set(&h, "Transport", transport);
+ return rtsp_send(hc, RTSP_CMD_SETUP, path, query, &h);
+}
+
+int
+rtsp_describe_decode( http_client_t *hc )
+{
+ http_arg_t *ra;
+
+ /* TODO: Probably rewrite the data to the htsmsg tree ? */
+ printf("describe: %i\n", hc->hc_code);
+ TAILQ_FOREACH(ra, &hc->hc_args, link)
+ printf(" %s: %s\n", ra->key, ra->val);
+ printf("data:\n%s\n", hc->hc_data);
+ return HTTP_CON_OK;
+}
From d9afa09b26736a5950a309092a198928a3ffa509 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Tue, 15 Apr 2014 19:35:12 +0200
Subject: [PATCH 13/42] Rewrite imagecache to use build-in http client (remove
curl)
- add the basic SSL peer certificate verification
---
configure | 27 ++---------------
src/http.h | 3 ++
src/httpc.c | 51 ++++++++++++++++++++++++++++++-
src/imagecache.c | 69 ++++++++++++++++++++++++++++++------------
src/tvhpoll.c | 2 ++
support/httpc-test.txt | 3 +-
6 files changed, 108 insertions(+), 47 deletions(-)
diff --git a/configure b/configure
index 839e53c9..161c7593 100755
--- a/configure
+++ b/configure
@@ -30,7 +30,6 @@ OPTIONS=(
"zlib:auto"
"libav:auto"
"inotify:auto"
- "curl:auto"
"epoll:auto"
"uriparser:auto"
"ccache:auto"
@@ -161,29 +160,11 @@ if enabled_or_auto zlib; then
fi
fi
-#
-# CURL
-#
-if enabled_or_auto curl; then
- if check_pkg libcurl; then
- if check_pkg nspr; then
- enable nspr
- fi
- enable curl
- elif enabled curl; then
- die "Curl development support not foun (use --disable-curl)"
- 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
+ enable upnp
fi
#
@@ -279,11 +260,7 @@ fi
# Icon caching
#
if enabled_or_auto imagecache; then
- if enabled curl; then
- enable imagecache
- elif enabled imagecache; then
- die "Libcurl support not found (use --disable-imagecache)"
- fi
+ enable imagecache
fi
#
diff --git a/src/http.h b/src/http.h
index 08ab0c3d..5ba944bf 100644
--- a/src/http.h
+++ b/src/http.h
@@ -264,6 +264,8 @@ struct http_client {
http_client_wcmd_t *hc_wcmd;
TAILQ_HEAD(,http_client_wcmd) hc_wqueue;
+ int hc_verify_peer; /* SSL - verify peer */
+
int hc_cseq; /* RTSP */
int hc_rcseq; /* RTSP - expected cseq */
char *hc_rtsp_session;
@@ -298,6 +300,7 @@ int http_client_send( http_client_t *hc, http_cmd_t cmd,
int http_client_simple( http_client_t *hc, const url_t *url);
int http_client_clear_state( http_client_t *hc );
int http_client_run( http_client_t *hc );
+void http_client_ssl_peer_verify( http_client_t *hc, int verify );
/*
* RTSP helpers
diff --git a/src/httpc.c b/src/httpc.c
index c9c845b6..a1ffe3ee 100644
--- a/src/httpc.c
+++ b/src/httpc.c
@@ -30,7 +30,9 @@
#include
#include
+#if ENABLE_TRACE
#define HTTPCLIENT_TESTSUITE 1
+#endif
struct http_client_ssl {
int connected;
@@ -345,11 +347,24 @@ http_client_ssl_send( http_client_t *hc, const void *buf, size_t len )
ssize_t r, r2;
int e;
+ if (hc->hc_verify_peer < 0)
+ http_client_ssl_peer_verify(hc, 1); /* default method - verify */
while (1) {
if (!ssl->connected) {
r = SSL_connect(ssl->ssl);
if (r > 0) {
ssl->connected = 1;
+ if (hc->hc_verify_peer > 0) {
+ if (SSL_get_peer_certificate(ssl->ssl) == NULL ||
+ SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
+ tvhlog(LOG_ERR, "httpc", "SSL peer verification failed (%s:%i)%s %li",
+ hc->hc_host, hc->hc_port,
+ SSL_get_peer_certificate(ssl->ssl) ? " X509" : "",
+ SSL_get_verify_result(ssl->ssl));
+ errno = EPERM;
+ return -1;
+ }
+ }
goto write;
}
} else {
@@ -1012,6 +1027,9 @@ http_client_redirected ( http_client_t *hc )
urlreset(&u);
return r;
}
+ r = hc->hc_verify_peer;
+ hc->hc_verify_peer = -1;
+ http_client_ssl_peer_verify(hc, r);
hc->hc_efd = efd;
}
@@ -1043,6 +1061,26 @@ http_client_simple( http_client_t *hc, const url_t *url )
&h, NULL, 0);
}
+void
+http_client_ssl_peer_verify( http_client_t *hc, int verify )
+{
+ struct http_client_ssl *ssl;
+
+ if (hc->hc_verify_peer < 0) {
+ hc->hc_verify_peer = verify ? 1 : 0;
+ if ((ssl = hc->hc_ssl) != NULL) {
+ if (!SSL_CTX_set_default_verify_paths(ssl->ctx))
+ tvherror("httpc", "SSL - unable to load CA certificates for verification");
+ SSL_CTX_set_verify_depth(ssl->ctx, 1);
+ SSL_CTX_set_verify(ssl->ctx,
+ hc->hc_verify_peer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
+ NULL);
+ }
+ } else {
+ tvherror("httpc", "SSL peer verification method must be set only once");
+ }
+}
+
/*
* Data thread
*/
@@ -1120,6 +1158,7 @@ http_client_reconnect
tvhlog(LOG_ERR, "httpc", "Unable to connect to %s:%i - %s", host, port, errbuf);
return -EINVAL;
}
+ tvhtrace("httpc", "Connected to %s:%i", host, port);
http_client_ssl_free(hc);
if (strcasecmp(scheme, "https") == 0 || strcasecmp(scheme, "rtsps") == 0) {
ssl = calloc(1, sizeof(*ssl));
@@ -1175,6 +1214,7 @@ http_client_connect
hc->hc_aux = aux;
hc->hc_io_size = 1024;
hc->hc_rtsp_stream_id = -1;
+ hc->hc_verify_peer = -1;
TAILQ_INIT(&hc->hc_args);
TAILQ_INIT(&hc->hc_wqueue);
@@ -1482,13 +1522,14 @@ http_client_testsuite_run( void )
FILE *fp;
int r, expected = HTTP_CON_DONE;
int handle_location = 0;
+ int peer_verify = 1;
path = getenv("TVHEADEND_HTTPC_TEST");
if (path == NULL)
path = TVHEADEND_DATADIR "/support/httpc-test.txt";
fp = fopen(path, "r");
if (fp == NULL) {
- tvhlog(LOG_ERR, "httpc", "Test: unable to open '%s': %s", path, strerror(errno));
+ tvhlog(LOG_NOTICE, "httpc", "Test: unable to open '%s': %s", path, strerror(errno));
return;
}
memset(&u1, 0, sizeof(u1));
@@ -1521,6 +1562,7 @@ http_client_testsuite_run( void )
port = 0;
expected = HTTP_CON_DONE;
handle_location = 0;
+ peer_verify = 1;
} else if (strcmp(s, "DataTransfer=all") == 0) {
data_transfer = 0;
} else if (strcmp(s, "DataTransfer=cont") == 0) {
@@ -1529,6 +1571,10 @@ http_client_testsuite_run( void )
handle_location = 0;
} else if (strcmp(s, "HandleLocation=1") == 0) {
handle_location = 1;
+ } else if (strcmp(s, "SSLPeerVerify=0") == 0) {
+ peer_verify = 0;
+ } else if (strcmp(s, "SSLPeerVerify=1") == 0) {
+ peer_verify = 1;
} else if (strncmp(s, "DataLimit=", 10) == 0) {
data_limit = atoll(s + 10);
} else if (strncmp(s, "Port=", 5) == 0) {
@@ -1558,6 +1604,8 @@ http_client_testsuite_run( void )
if (urlparse(s + 4, &u1) < 0)
goto fatal;
} else if (strncmp(s, "Command=", 8) == 0) {
+ if (strcmp(s + 8, "EXIT") == 0)
+ break;
if (u1.host == NULL || u1.host[0] == '\0') {
fprintf(stderr, "HTTPCTS: Define URL\n");
goto fatal;
@@ -1579,6 +1627,7 @@ http_client_testsuite_run( void )
} else {
fprintf(stderr, "HTTPCTS: Connected to %s:%i\n", hc->hc_host, hc->hc_port);
}
+ http_client_ssl_peer_verify(hc, peer_verify);
}
fprintf(stderr, "HTTPCTS Send: Cmd=%s Ver=%s Host=%s Path=%s\n",
http_cmd2str(cmd), http_ver2str(ver), http_arg_get(&args, "Host"), u1.path);
diff --git a/src/imagecache.c b/src/imagecache.c
index bc219acd..cf17f01d 100644
--- a/src/imagecache.c
+++ b/src/imagecache.c
@@ -35,12 +35,7 @@
#include "redblack.h"
#include "notify.h"
#include "prop.h"
-
-#if ENABLE_IMAGECACHE
-#define CURL_STATICLIB
-#include
-#include
-#endif
+#include "http.h"
/*
* Image metadata
@@ -139,10 +134,15 @@ imagecache_image_add ( imagecache_image_t *img )
static int
imagecache_image_fetch ( imagecache_image_t *img )
{
- int res = 1;
- CURL *curl;
+ int res = 1, r;
FILE *fp;
+ url_t url;
char tmp[256], path[256];
+ tvhpoll_event_t ev;
+ tvhpoll_t *efd = NULL;
+ http_client_t *hc;
+
+ memset(&url, 0, sizeof(url));
/* Open file */
if (hts_settings_buildpath(path, sizeof(path), "imagecache/data/%d",
@@ -156,25 +156,54 @@ imagecache_image_fetch ( imagecache_image_t *img )
/* Build command */
tvhlog(LOG_DEBUG, "imagecache", "fetch %s", img->url);
- curl = curl_easy_init();
- curl_easy_setopt(curl, CURLOPT_URL, img->url);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
- curl_easy_setopt(curl, CURLOPT_USERAGENT, "TVHeadend");
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120);
- curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
- curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
- if (imagecache_conf.ignore_sslcert)
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ memset(&url, 0, sizeof(url));
+ if (urlparse(img->url, &url)) {
+ tvherror("imagecache", "Unable to parse url '%s'", img->url);
+ goto error;
+ }
/* Fetch (release lock, incase of delays) */
pthread_mutex_unlock(&global_lock);
- res = curl_easy_perform(curl);
- curl_easy_cleanup(curl);
- fclose(fp);
+
+ hc = http_client_connect(NULL, HTTP_VERSION_1_1, url.scheme,
+ url.host, url.port);
+ if (hc == NULL)
+ goto error;
+
+ http_client_ssl_peer_verify(hc, imagecache_conf.ignore_sslcert ? 0 : 1);
+ hc->hc_handle_location = 1;
+ hc->hc_data_limit = 256*1024;
+ efd = tvhpoll_create(1);
+ hc->hc_efd = efd;
+
+ r = http_client_simple(hc, &url);
+ if (r < 0)
+ goto error;
+
+ while (tvheadend_running) {
+ r = tvhpoll_wait(efd, &ev, 1, -1);
+ if (r < 0)
+ break;
+ if (r == 0)
+ continue;
+ r = http_client_run(hc);
+ if (r < 0)
+ break;
+ if (r == HTTP_CON_DONE) {
+ if (hc->hc_code == HTTP_STATUS_OK && hc->hc_data_size > 0) {
+ fwrite(hc->hc_data, hc->hc_data_size, 1, fp);
+ res = 0;
+ }
+ break;
+ }
+ }
+
pthread_mutex_lock(&global_lock);
/* Process */
error:
+ urlreset(&url);
+ tvhpoll_destroy(efd);
img->state = IDLE;
time(&img->updated); // even if failed (possibly request sooner?)
if (res) {
diff --git a/src/tvhpoll.c b/src/tvhpoll.c
index 29e58936..1783dc12 100644
--- a/src/tvhpoll.c
+++ b/src/tvhpoll.c
@@ -91,6 +91,8 @@ tvhpoll_create ( size_t n )
void tvhpoll_destroy ( tvhpoll_t *tp )
{
+ if (tp == NULL)
+ return;
#if ENABLE_EPOLL || ENABLE_KQUEUE
free(tp->ev);
close(tp->fd);
diff --git a/support/httpc-test.txt b/support/httpc-test.txt
index 26bb6e9b..418bc254 100644
--- a/support/httpc-test.txt
+++ b/support/httpc-test.txt
@@ -7,7 +7,9 @@
# DataTransfer=cont continuous passing data to the data_received callback
# DataLimit= limit data to these bytes
# ExpectedError= expected error
+# SSLPeerVerify= Enable SSL/TLS peer verification (0 or 1)
# Reset=1 reset the initial state - close the current keep-alive conn
+# Command= HTTP/RTSP command or EXIT
#
Header=Connection: close
@@ -78,4 +80,3 @@ DataLimit=10
HandleLocation=1
ExpectedError=EOVERFLOW
Command=GET
-
From 521c9e35248c8e959748311e047c5a9a8419c9a1 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 09:06:47 +0200
Subject: [PATCH 14/42] IPTV: tune the http client settings
- increase the chunk size to 128KB
- allow HTTP redirects
---
src/input/mpegts/iptv/iptv_http.c | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/input/mpegts/iptv/iptv_http.c b/src/input/mpegts/iptv/iptv_http.c
index 770dff4a..9aed89be 100644
--- a/src/input/mpegts/iptv/iptv_http.c
+++ b/src/input/mpegts/iptv/iptv_http.c
@@ -27,9 +27,12 @@
static int
iptv_http_header ( http_client_t *hc )
{
- pthread_mutex_lock(&global_lock);
- iptv_input_mux_started(hc->hc_aux);
- pthread_mutex_unlock(&global_lock);
+ /* multiple headers for redirections */
+ if (hc->hc_code == HTTP_STATUS_OK) {
+ pthread_mutex_lock(&global_lock);
+ iptv_input_mux_started(hc->hc_aux);
+ pthread_mutex_unlock(&global_lock);
+ }
return 0;
}
@@ -67,9 +70,11 @@ iptv_http_start
if (!(hc = http_client_connect(im, HTTP_VERSION_1_1, u->scheme,
u->host, u->port)))
return SM_CODE_TUNING_FAILED;
- hc->hc_hdr_received = iptv_http_header;
- hc->hc_data_received = iptv_http_data;
- http_client_register(hc);
+ hc->hc_hdr_received = iptv_http_header;
+ hc->hc_data_received = iptv_http_data;
+ hc->hc_handle_location = 1; /* allow redirects */
+ hc->hc_chunk_size = 128*1024; /* increase buffering */
+ http_client_register(hc); /* register to the HTTP thread */
r = http_client_simple(hc, u);
if (r < 0) {
http_client_close(hc);
From eb7b4057f5da6584a491975d9ea77bc163acf469 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 11:10:05 +0200
Subject: [PATCH 15/42] Fix the compilation error for URIPARSER
---
src/url.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/url.c b/src/url.c
index 53ebdc4f..8f0c38d3 100644
--- a/src/url.c
+++ b/src/url.c
@@ -106,7 +106,7 @@ urlparse ( const char *str, url_t *url )
url->port = 0;
path = uri.pathHead;
while (path) {
- uri_copy(buf, path->text);
+ uri_copy_static(buf, path->text);
url->path = realloc(url->path, strlen(url->path) + strlen(buf) + 2);
strcat(url->path, "/");
strcat(url->path, buf);
@@ -127,7 +127,7 @@ urlparse ( const char *str, url_t *url )
}
void
-urlparse_free( void )
+urlparse_done( void )
{
}
From 28bd7e10e717ea42bf0a05b6a272bc0edd145177 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 11:37:18 +0200
Subject: [PATCH 16/42] SAT>IP define the recvmmsg syscall for some libc
implementations
---
configure | 12 ++++++++++
src/input/mpegts/satip/satip_frontend.c | 30 +++++++++++++++++++++++++
2 files changed, 42 insertions(+)
diff --git a/configure b/configure
index 161c7593..8d0e47cc 100755
--- a/configure
+++ b/configure
@@ -113,6 +113,18 @@ int test(void)
return 0;
}
'
+
+check_cc_snippet recvmmsg '
+#define _GNU_SOURCE
+#include
+#include
+#define TEST test
+int test(void)
+{
+ recvmmsg(0, NULL, 0, 0, NULL);
+ return 0;
+}
+'
#
# Python
#
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 859e556c..4f8ee25a 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -26,6 +26,36 @@
#include "http.h"
#include "satip_private.h"
+#ifndef CONFIG_RECVMMSG
+
+#ifdef __linux__
+
+/* define the syscall - works only for linux */
+
+#include
+
+struct mmsghdr {
+ struct msghdr msg_hdr;
+ unsigned int msg_len;
+};
+
+int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+ unsigned int flags, struct timespec *timeout);
+
+int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+ unsigned int flags, struct timespec *timeout)
+{
+ return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout);
+}
+
+#else /* not __linux__ */
+
+#error "Add recvmmsg() support for your platform!!!"
+
+#endif
+
+#endif /* ENABLE_RECVMMSG */
+
static int
satip_frontend_tune1
( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi );
From 35afb618e65861c14f5b90866bf040ed5e172fd1 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 17:32:04 +0200
Subject: [PATCH 17/42] http client: Fix some compilation errors
---
src/httpc.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/httpc.c b/src/httpc.c
index a1ffe3ee..52226be0 100644
--- a/src/httpc.c
+++ b/src/httpc.c
@@ -30,6 +30,10 @@
#include
#include
+#ifndef SSL_OP_NO_COMPRESSION
+#define SSL_OP_NO_COMPRESSION 0
+#endif
+
#if ENABLE_TRACE
#define HTTPCLIENT_TESTSUITE 1
#endif
@@ -1344,7 +1348,7 @@ http_client_testsuite_conn_closed( http_client_t *hc, int result )
static int
http_client_testsuite_data_complete( http_client_t *hc )
{
- fprintf(stderr, "HTTPCTS: Data Complete (code=%i, data=%p, data_size=%li)\n",
+ fprintf(stderr, "HTTPCTS: Data Complete (code=%i, data=%p, data_size=%zi)\n",
hc->hc_code, hc->hc_data, hc->hc_data_size);
return 0;
}
@@ -1352,7 +1356,7 @@ http_client_testsuite_data_complete( http_client_t *hc )
static int
http_client_testsuite_data_received( http_client_t *hc, void *data, size_t len )
{
- fprintf(stderr, "HTTPCTS: Data received (len=%li)\n", len);
+ fprintf(stderr, "HTTPCTS: Data received (len=%zi)\n", len);
/* check, if the data memory area is OK */
memset(data, 0xa5, len);
return 0;
@@ -1501,8 +1505,12 @@ static struct strtab ERRNO_tab[] = {
{ "EKEYREJECTED", EKEYREJECTED },
{ "EOWNERDEAD", EOWNERDEAD },
{ "ENOTRECOVERABLE", ENOTRECOVERABLE },
+#ifdef ERFKILL
{ "ERFKILL", ERFKILL },
+#endif
+#ifdef EHWPOISON
{ "EHWPOISON", EHWPOISON },
+#endif
};
void
From 2e491c5e7e1c3150ecb581b4d97d4ce7ccf2e5e4 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 17:57:56 +0200
Subject: [PATCH 18/42] SAT>IP : Add preliminary DVB-C code
---
src/input/mpegts/satip/satip.c | 8 ++++++++
src/input/mpegts/satip/satip_frontend.c | 14 ++++++++++++++
src/input/mpegts/satip/satip_rtsp.c | 13 ++++++++++++-
3 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c
index c4798ab4..fd4b1dd5 100644
--- a/src/input/mpegts/satip/satip.c
+++ b/src/input/mpegts/satip/satip.c
@@ -340,6 +340,9 @@ satip_device_create( satip_device_info_t *info )
} else if (strncmp(argv[i], "DVBT-", 5) == 0) {
type = DVB_TYPE_T;
m = atoi(argv[i] + 5);
+ } else if (strncmp(argv[i], "DVBC-", 5) == 0) {
+ type = DVB_TYPE_C;
+ m = atoi(argv[i] + 5);
}
if (type == DVB_TYPE_NONE) {
tvhlog(LOG_ERR, "satip", "%s: bad tuner type [%s]", sd->sd_info.addr, argv[i]);
@@ -511,6 +514,11 @@ satip_discovery_http_closed(http_client_t *hc, int errn)
return;
}
+#if ENABLE_TRACE
+ tvhtrace("satip", "received XML description from %s", hc->hc_host);
+ tvhlog_hexdump("satip", hc->hc_data, hc->hc_data_size);
+#endif
+
s = hc->hc_data + hc->hc_data_size - 1;
while (s != hc->hc_data && *s != '/')
s--;
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 4f8ee25a..8eaf47de 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -162,6 +162,16 @@ const idclass_t satip_frontend_dvbs_class =
}
};
+const idclass_t satip_frontend_dvbc_class =
+{
+ .ic_super = &satip_frontend_class,
+ .ic_class = "satip_frontend_dvbc",
+ .ic_caption = "SAT>IP DVB-C Frontend",
+ .ic_properties = (const property_t[]){
+ {}
+ }
+};
+
/* **************************************************************************
* Class methods
* *************************************************************************/
@@ -419,6 +429,8 @@ satip_frontend_network_list ( mpegts_input_t *mi )
idc = &dvb_network_dvbt_class;
else if (lfe->sf_type == DVB_TYPE_S)
idc = &dvb_network_dvbs_class;
+ else if (lfe->sf_type == DVB_TYPE_C)
+ idc = &dvb_network_dvbc_class;
else
return NULL;
@@ -1071,6 +1083,8 @@ satip_frontend_create
idc = &satip_frontend_dvbs_class;
else if (type == DVB_TYPE_T)
idc = &satip_frontend_dvbt_class;
+ else if (type == DVB_TYPE_C)
+ idc = &satip_frontend_dvbc_class;
else {
tvherror("satip", "unknown FE type %d", type);
return NULL;
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
index d5bc0d45..4d52a74d 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -77,6 +77,9 @@ satip_rtsp_setup( http_client_t *hc, int src, int fe,
{ .t = DVB_SYS_DVBT2, "dvbt2" },
{ .t = DVB_SYS_DVBS, "dvbs" },
{ .t = DVB_SYS_DVBS2, "dvbs2" },
+ { .t = DVB_SYS_DVBC_ANNEX_A, "dvbc" },
+ { .t = DVB_SYS_DVBC_ANNEX_B, "dvbc" },
+ { .t = DVB_SYS_DVBC_ANNEX_C, "dvbc" },
{ .t = TABLE_EOD }
};
static tvh2satip_t pol[] = {
@@ -157,7 +160,7 @@ satip_rtsp_setup( http_client_t *hc, int src, int 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);
+ 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" );
@@ -165,6 +168,14 @@ satip_rtsp_setup( http_client_t *hc, int src, int fe,
ADD(dmc_fe_rolloff, ro, "0.35");
if (dmc->dmc_fe_pilot != DVB_PILOT_AUTO)
ADD(dmc_fe_pilot, plts, "auto");
+ } else if (dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_A ||
+ dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_B ||
+ dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_C) {
+ satip_rtsp_add_val("sr", buf, dmc->u.dmc_fe_qam.symbol_rate);
+ ADD(dmc_fe_delsys, msys, "dvbc");
+ ADD(dmc_fe_modulation, mtype, "64qam");
+ /* missing plp */
+ ADD(u.dmc_fe_qpsk.fec_inner, fec, "auto");
} else {
if (dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_AUTO &&
dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_NONE)
From 286dd1c1dd7717c4c509f432c5851948054f4000 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 18:09:28 +0200
Subject: [PATCH 19/42] SAT>IP: Add support for kernels before 2.6.33
---
src/input/mpegts/satip/satip_frontend.c | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 8eaf47de..aa4dc3fd 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -26,6 +26,8 @@
#include "http.h"
#include "satip_private.h"
+#define PKTS 64
+
#ifndef CONFIG_RECVMMSG
#ifdef __linux__
@@ -42,19 +44,38 @@ struct mmsghdr {
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
unsigned int flags, struct timespec *timeout);
+#ifdef __NR_recvmmsg
+
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
unsigned int flags, struct timespec *timeout)
{
return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout);
}
+#else
+
+#undef PKTS
+#define PKTS 1
+/* receive only single packet */
+int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+ unsigned int flags, struct timespec *timeout)
+{
+ ssize_t r = recvmsg(sockfd, &msgvec->msg_hdr, flags);
+ if (r < 0)
+ return r;
+ msgvec->msg_len = r;
+ return 1;
+}
+
+#endif
+
#else /* not __linux__ */
#error "Add recvmmsg() support for your platform!!!"
#endif
-#endif /* ENABLE_RECVMMSG */
+#endif /* !CONFIG_RECVMMSG */
static int
satip_frontend_tune1
@@ -693,7 +714,6 @@ satip_frontend_pid_changed( http_client_t *rtsp,
static void *
satip_frontend_input_thread ( void *aux )
{
-#define PKTS 64
#define HTTP_CMD_NONE 9874
satip_frontend_t *lfe = aux;
mpegts_mux_instance_t *mmi = lfe->sf_mmi;
From a3558b3fe06b4e14fb5ce5bcf2690ba4aee81ee9 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 18:16:34 +0200
Subject: [PATCH 20/42] udp: fix the bad getaddrinfo call
---
src/udp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/udp.c b/src/udp.c
index a52d84e1..0e4a93a7 100644
--- a/src/udp.c
+++ b/src/udp.c
@@ -55,7 +55,7 @@ udp_resolve( udp_connection_t *uc, int receiver )
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
- x = getaddrinfo(uc->host ? uc->host : "*", port_buf, &hints, &res);
+ x = getaddrinfo(uc->host, port_buf, &hints, &res);
if (x < 0) {
tvhlog(LOG_ERR, uc->subsystem, "getaddrinfo: %s: %s",
uc->host != NULL ? uc->host : "*",
From 20e1abc00c01ee708a559a4d4e0cde1d9f4a5995 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 19:17:28 +0200
Subject: [PATCH 21/42] http client: allow HTTP 1.1 to HTTP 1.0 switch
---
src/httpc.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/httpc.c b/src/httpc.c
index 52226be0..4be83d74 100644
--- a/src/httpc.c
+++ b/src/httpc.c
@@ -905,8 +905,13 @@ header:
return http_client_flush(hc, -EINVAL);
if ((ver = http_str2ver(argv[0])) < 0)
return http_client_flush(hc, -EINVAL);
- if (ver != hc->hc_version)
- return http_client_flush(hc, -EINVAL);
+ if (ver != hc->hc_version) {
+ /* 1.1 -> 1.0 transition allowed */
+ if (hc->hc_version == HTTP_VERSION_1_1 && ver == HTTP_VERSION_1_0)
+ hc->hc_version = ver;
+ else
+ return http_client_flush(hc, -EINVAL);
+ }
if ((hc->hc_code = atoi(argv[1])) < 200)
return http_client_flush(hc, -EINVAL);
while ((p = strtok_r(NULL, "\r\n", &saveptr)) != NULL) {
From e7082c8d2e2a6fe06ba964a29d6475f5e2b8dab2 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 19:29:37 +0200
Subject: [PATCH 22/42] http client: fix the error code passed to conn_closed
callback
---
src/httpc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/httpc.c b/src/httpc.c
index 4be83d74..d949562e 100644
--- a/src/httpc.c
+++ b/src/httpc.c
@@ -182,9 +182,9 @@ http_client_cmd_destroy( http_client_t *hc, http_client_wcmd_t *cmd )
static int
http_client_flush( http_client_t *hc, int result )
{
+ hc->hc_result = result;
if (result < 0)
http_client_shutdown(hc, 0);
- hc->hc_result = result;
hc->hc_in_data = 0;
hc->hc_hsize = 0;
hc->hc_csize = 0;
From f85a8fc4aa711d5608c2ef78e23f6594185457ad Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 22:33:02 +0200
Subject: [PATCH 23/42] SAT>IP: Add the possibility to override tuner type
(DVB-T <-> DVB-C)
---
src/idnode.c | 1 +
src/input/mpegts/satip/satip.c | 25 +++++++-
src/input/mpegts/satip/satip_frontend.c | 83 +++++++++++++++++++++++--
src/input/mpegts/satip/satip_private.h | 13 ++--
4 files changed, 109 insertions(+), 13 deletions(-)
diff --git a/src/idnode.c b/src/idnode.c
index f068dba9..13ba418c 100644
--- a/src/idnode.c
+++ b/src/idnode.c
@@ -587,6 +587,7 @@ idnode_filter_clear
else
free(ele->u.s);
}
+ free(ele->key);
free(ele);
}
}
diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c
index fd4b1dd5..0e3f3272 100644
--- a/src/input/mpegts/satip/satip.c
+++ b/src/input/mpegts/satip/satip.c
@@ -28,6 +28,8 @@
#include
#include
+static void satip_device_discovery_start( void );
+
/*
* SAT-IP client
*/
@@ -404,6 +406,8 @@ satip_device_destroy( satip_device_t *sd )
lock_assert(&global_lock);
+ gtimer_disarm(&sd->sd_destroy_timer);
+
while ((lfe = TAILQ_FIRST(&sd->sd_frontends)) != NULL)
satip_frontend_delete(lfe);
@@ -431,6 +435,19 @@ satip_device_destroy( satip_device_t *sd )
free(sd);
}
+static void
+satip_device_destroy_cb( void *aux )
+{
+ satip_device_destroy((satip_device_t *)aux);
+ satip_device_discovery_start();
+}
+
+void
+satip_device_destroy_later( satip_device_t *sd, int after )
+{
+ gtimer_arm_ms(&sd->sd_destroy_timer, satip_device_destroy_cb, sd, after);
+}
+
/*
* Discovery job
*/
@@ -795,6 +812,12 @@ ST: urn:ses-com:device:SatIPServer:1\r\n\
#undef MSG
}
+static void
+satip_device_discovery_start( void )
+{
+ gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1);
+}
+
/*
* Initialization
*/
@@ -802,7 +825,7 @@ ST: urn:ses-com:device:SatIPServer:1\r\n\
void satip_init ( void )
{
TAILQ_INIT(&satip_discoveries);
- gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1);
+ satip_device_discovery_start();
}
void satip_done ( void )
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index aa4dc3fd..80ebee2e 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -92,6 +92,39 @@ satip_frontend_class_save ( idnode_t *in )
satip_device_save(la);
}
+static int
+satip_frontend_set_new_type
+ ( satip_frontend_t *lfe, const char *type )
+{
+ free(lfe->sf_type_override);
+ lfe->sf_type_override = strdup(type);
+ satip_device_destroy_later(lfe->sf_device, 100);
+ return 1;
+}
+
+static int
+satip_frontend_class_override_set( void *obj, const void * p )
+{
+ satip_frontend_t *lfe = obj;
+ const char *s = p;
+
+ if (lfe->sf_type_override == NULL) {
+ if (strlen(p) > 0)
+ return satip_frontend_set_new_type(lfe, s);
+ } else if (strcmp(lfe->sf_type_override, s))
+ return satip_frontend_set_new_type(lfe, s);
+ return 0;
+}
+
+static htsmsg_t *
+satip_frontend_class_override_enum( void * p )
+{
+ htsmsg_t *m = htsmsg_create_list();
+ htsmsg_add_str(m, NULL, "DVB-T");
+ htsmsg_add_str(m, NULL, "DVB-C");
+ return m;
+}
+
const idclass_t satip_frontend_class =
{
.ic_super = &mpegts_input_class,
@@ -128,6 +161,14 @@ const idclass_t satip_frontend_dvbt_class =
.ic_class = "satip_frontend_dvbt",
.ic_caption = "SAT>IP DVB-T Frontend",
.ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .id = "fe_override",
+ .name = "Network Type",
+ .set = satip_frontend_class_override_set,
+ .list = satip_frontend_class_override_enum,
+ .off = offsetof(satip_frontend_t, sf_type_override),
+ },
{}
}
};
@@ -189,6 +230,14 @@ const idclass_t satip_frontend_dvbc_class =
.ic_class = "satip_frontend_dvbc",
.ic_caption = "SAT>IP DVB-C Frontend",
.ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .id = "fe_override",
+ .name = "Network Type",
+ .set = satip_frontend_class_override_set,
+ .list = satip_frontend_class_override_enum,
+ .off = offsetof(satip_frontend_t, sf_type_override),
+ },
{}
}
};
@@ -1087,10 +1136,23 @@ 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;
+ const char *uuid = NULL, *override = NULL;
char id[12], lname[256];
satip_frontend_t *lfe;
+ int i;
+ /* Override type */
+ snprintf(id, sizeof(id), "override #%d", num);
+ if (conf && type != DVB_TYPE_S) {
+ override = htsmsg_get_str(conf, id);
+ if (override) {
+ i = dvb_str2type(override);
+ if ((i == DVB_TYPE_T || i == DVB_TYPE_C || DVB_TYPE_S) && i != type)
+ type = i;
+ else
+ override = NULL;
+ }
+ }
/* Internal config ID */
snprintf(id, sizeof(id), "%s #%d", dvb_type2str(type), num);
if (conf)
@@ -1114,9 +1176,11 @@ satip_frontend_create
// 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_device = sd;
lfe->sf_number = num;
lfe->sf_type = type;
lfe->sf_type_t2 = t2;
+ lfe->sf_type_override = override ? strdup(override) : NULL;
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);
@@ -1132,9 +1196,13 @@ satip_frontend_create
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);
+ if (!lfe->mi_name ||
+ (strncmp(lfe->mi_name, "SAT>IP ", 7) == 0 &&
+ strstr(lfe->mi_name, " Tuner ") &&
+ strstr(lfe->mi_name, " #"))) {
+ snprintf(lname, sizeof(lname), "SAT>IP %s Tuner #%i (%s)",
+ dvb_type2str(type), num, sd->sd_info.addr);
+ free(lfe->mi_name);
lfe->mi_name = strdup(lname);
}
@@ -1169,10 +1237,15 @@ satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe )
satip_satconf_save(lfe, m);
if (lfe->ti_id.in_class == &satip_frontend_dvbs_class)
htsmsg_delete_field(m, "networks");
+ htsmsg_delete_field(m, "fe_override");
/* Add to list */
snprintf(id, sizeof(id), "%s #%d", dvb_type2str(lfe->sf_type), lfe->sf_number);
htsmsg_add_msg(fe, id, m);
+ if (lfe->sf_type_override) {
+ snprintf(id, sizeof(id), "override #%d", lfe->sf_number);
+ htsmsg_add_str(fe, id, lfe->sf_type_override);
+ }
}
void
@@ -1194,6 +1267,8 @@ satip_frontend_delete ( satip_frontend_t *lfe )
/* Delete satconf */
satip_satconf_destroy(lfe);
+ free(lfe->sf_type_override);
+
/* 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
index 7b096b52..5e35f985 100644
--- a/src/input/mpegts/satip/satip_private.h
+++ b/src/input/mpegts/satip/satip_private.h
@@ -58,6 +58,8 @@ struct satip_device
{
tvh_hardware_t;
+ gtimer_t sd_destroy_timer;
+
/*
* Adapter info
*/
@@ -76,14 +78,6 @@ struct satip_device
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
@@ -102,6 +96,7 @@ struct satip_frontend
int sf_number;
dvb_fe_type_t sf_type;
int sf_type_t2;
+ char *sf_type_override;
int sf_udp_rtp_port;
int sf_fullmux;
@@ -171,6 +166,8 @@ void satip_device_save ( satip_device_t *sd );
void satip_device_destroy ( satip_device_t *sd );
+void satip_device_destroy_later( satip_device_t *sd, int after_ms );
+
satip_frontend_t *
satip_frontend_create
( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num );
From a0fb77d83a5b9fcac8e321cd5c8859c8e443aa29 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 16 Apr 2014 22:48:41 +0200
Subject: [PATCH 24/42] urlparser: Fixes in URIPARSER
---
src/url.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/src/url.c b/src/url.c
index 8f0c38d3..c9a8cd60 100644
--- a/src/url.c
+++ b/src/url.c
@@ -91,6 +91,7 @@ urlparse ( const char *str, url_t *url )
if (x.first) {\
size_t len = x.afterLast - x.first;\
strncpy(y, x.first, len);\
+ y[len] = '\0';\
} else {\
y[0] = '\0';\
}
@@ -107,7 +108,10 @@ urlparse ( const char *str, url_t *url )
path = uri.pathHead;
while (path) {
uri_copy_static(buf, path->text);
- url->path = realloc(url->path, strlen(url->path) + strlen(buf) + 2);
+ if (url->path)
+ url->path = realloc(url->path, strlen(url->path) + strlen(buf) + 2);
+ else
+ url->path = calloc(1, strlen(buf) + 2);
strcat(url->path, "/");
strcat(url->path, buf);
path = path->next;
@@ -115,10 +119,12 @@ urlparse ( const char *str, url_t *url )
// TODO: query/fragment
/* Split user/pass */
- s = strstr(url->user, ":");
- if (s) {
- strcpy(url->pass, s+1);
- *s = 0;
+ if (url->user) {
+ s = strstr(url->user, ":");
+ if (s) {
+ strcpy(url->pass, s+1);
+ *s = 0;
+ }
}
/* Cleanup */
From 18c46144f0596a19800c7f6312141d536893e694 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Thu, 17 Apr 2014 09:35:21 +0200
Subject: [PATCH 25/42] scanfile: fix a memory leak
---
src/input/mpegts/scanfile.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/input/mpegts/scanfile.c b/src/input/mpegts/scanfile.c
index bf540557..6e9c6016 100644
--- a/src/input/mpegts/scanfile.c
+++ b/src/input/mpegts/scanfile.c
@@ -469,7 +469,7 @@ scanfile_find ( const char *id )
/* Type */
if (!(tok = strtok_r(tmp, "/", &s)))
- return NULL;
+ goto fail;
if (!strcasecmp(tok, "dvbt"))
l = &scanfile_regions_DVBT;
else if (!strcasecmp(tok, "dvbc"))
@@ -479,22 +479,27 @@ scanfile_find ( const char *id )
else if (!strcasecmp(tok, "atsc"))
l = &scanfile_regions_ATSC;
else
- return NULL;
+ goto fail;
/* Region */
if (!(tok = strtok_r(NULL, "/", &s)))
- return NULL;
+ goto fail;
LIST_FOREACH(r, l, sfr_link)
if (!strcmp(r->sfr_id, tok))
break;
- if (!r) return NULL;
+ if (!r) goto fail;
/* Network */
if (!(tok = strtok_r(NULL, "/", &s)))
- return NULL;
+ goto fail;
LIST_FOREACH(n, &r->sfr_networks, sfn_link)
if (!strcmp(n->sfn_id, tok))
break;
+ free(tmp);
return n;
+
+fail:
+ free(tmp);
+ return NULL;
}
From ff145046f7a33a2aceb4f790351c9eb3f18dcd17 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Thu, 17 Apr 2014 22:12:04 +0200
Subject: [PATCH 26/42] SAT>IP: Added OctopusNet pids hack
---
src/input/mpegts/satip/satip.c | 10 +++++++++
src/input/mpegts/satip/satip_frontend.c | 28 +++++++++++++++++++++++--
src/input/mpegts/satip/satip_private.h | 4 +++-
src/input/mpegts/satip/satip_rtsp.c | 9 ++++++--
4 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c
index 0e3f3272..9a238543 100644
--- a/src/input/mpegts/satip/satip.c
+++ b/src/input/mpegts/satip/satip.c
@@ -105,6 +105,13 @@ const idclass_t satip_device_class =
.opts = PO_ADVANCED,
.off = offsetof(satip_device_t, sd_pids_deladd),
},
+ {
+ .type = PT_BOOL,
+ .id = "pids0",
+ .name = "PIDs in setup",
+ .opts = PO_ADVANCED,
+ .off = offsetof(satip_device_t, sd_pids0),
+ },
{
.type = PT_STR,
.id = "addr",
@@ -263,6 +270,9 @@ satip_device_hack( satip_device_t *sd )
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");
+ } else if (strstr(sd->sd_info.location, ":8888/octonet.xml")) {
+ /* OctopusNet requires pids in the SETUP RTSP command */
+ sd->sd_pids0 = 1;
}
}
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 80ebee2e..756908f7 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -531,6 +531,11 @@ satip_frontend_decode_rtcp( satip_frontend_t *lfe, const char *name,
* DVB-T:
* ver=1.1;tuner=,,,,,,,,\
* ,,,,,;pids=,...,
+ *
+ * DVB-C (OctopusNet):
+ * ver=0.9;tuner=,<0>,,<0>,,,,;pids=,...
+ * example:
+ * ver=0.9;tuner=1,0,1,0,362.000,6900,dvbc,256qam;pids=0,1,16,17,18
*/
/* level:
@@ -567,7 +572,25 @@ satip_frontend_decode_rtcp( satip_frontend_t *lfe, const char *name,
s = (char *)rtcp + 16;
tvhtrace("satip", "Status string: '%s'", s);
status = SIGNAL_NONE;
- if (strncmp(s, "ver=1.0;", 8) == 0) {
+ if (strncmp(s, "ver=0.9;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]);
+ if (status == SIGNAL_GOOD &&
+ mmi->mmi_stats.signal == 0 && mmi->mmi_stats.snr == 0) {
+ /* some values that we're tuned */
+ mmi->mmi_stats.signal = 50;
+ mmi->mmi_stats.snr = 12;
+ }
+ goto ok;
+ } else if (strncmp(s, "ver=1.0;", 8) == 0) {
if ((s = strstr(s + 8, ";tuner=")) == NULL)
return;
s += 7;
@@ -825,7 +848,8 @@ satip_frontend_input_thread ( void *aux )
r = satip_rtsp_setup(rtsp,
lfe->sf_position, lfe->sf_number,
- lfe->sf_rtp_port, &lm->lm_tuning);
+ lfe->sf_rtp_port, &lm->lm_tuning,
+ lfe->sf_device->sd_pids0);
if (r < 0) {
tvherror("satip", "%s - failed to tune", buf);
return NULL;
diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h
index 5e35f985..a1051c2b 100644
--- a/src/input/mpegts/satip/satip_private.h
+++ b/src/input/mpegts/satip/satip_private.h
@@ -78,6 +78,7 @@ struct satip_device
int sd_pids_len;
int sd_pids_deladd;
int sd_sig_scale;
+ int sd_pids0;
};
struct satip_frontend
@@ -202,7 +203,8 @@ int satip_satconf_get_position
int
satip_rtsp_setup( http_client_t *hc,
int src, int fe, int udp_port,
- const dvb_mux_conf_t *dmc );
+ const dvb_mux_conf_t *dmc,
+ int pids0 );
int
satip_rtsp_play( http_client_t *hc, const char *pids,
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
index 4d52a74d..ed0bc831 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -70,7 +70,7 @@ satip_rtsp_add_val(const char *name, char *buf, uint32_t val)
int
satip_rtsp_setup( http_client_t *hc, int src, int fe,
- int udp_port, const dvb_mux_conf_t *dmc )
+ int udp_port, const dvb_mux_conf_t *dmc, int pids0 )
{
static tvh2satip_t msys[] = {
{ .t = DVB_SYS_DVBT, "dvbt" },
@@ -175,7 +175,10 @@ satip_rtsp_setup( http_client_t *hc, int src, int fe,
ADD(dmc_fe_delsys, msys, "dvbc");
ADD(dmc_fe_modulation, mtype, "64qam");
/* missing plp */
- ADD(u.dmc_fe_qpsk.fec_inner, fec, "auto");
+ if (dmc->u.dmc_fe_qam.fec_inner != DVB_FEC_NONE &&
+ dmc->u.dmc_fe_qam.fec_inner != DVB_FEC_AUTO)
+ /* note: OctopusNet device does not handle 'fec=auto' */
+ ADD(u.dmc_fe_qam.fec_inner, fec, "auto");
} else {
if (dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_AUTO &&
dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_NONE)
@@ -192,6 +195,8 @@ satip_rtsp_setup( http_client_t *hc, int src, int fe,
dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_NONE)
ADD(u.dmc_fe_ofdm.guard_interval, gi, "18");
}
+ if (pids0)
+ strcat(buf, "&pids=0");
tvhtrace("satip", "setup params - %s", buf);
if (hc->hc_rtsp_stream_id >= 0)
snprintf(stream = _stream, sizeof(_stream), "/stream=%li",
From 9f962b7e1bbbd9f9df472e56aa5864cb10c60600 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Thu, 17 Apr 2014 23:28:56 +0200
Subject: [PATCH 27/42] upnp: use data.ptr instead data.u64 for poll events
---
src/upnp.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/upnp.c b/src/upnp.c
index d2ef9c35..849abbe0 100644
--- a/src/upnp.c
+++ b/src/upnp.c
@@ -126,10 +126,10 @@ upnp_thread( void *aux )
memset(&ev, 0, sizeof(ev));
ev[0].fd = multicast->fd;
ev[0].events = TVHPOLL_IN;
- ev[0].data.u64 = (uint64_t)multicast;
+ ev[0].data.ptr = multicast;
ev[1].fd = unicast->fd;
ev[1].events = TVHPOLL_IN;
- ev[1].data.u64 = (uint64_t)unicast;
+ ev[1].data.ptr = unicast;
tvhpoll_add(poll, ev, 2);
while (upnp_running && multicast->fd >= 0) {
@@ -137,7 +137,7 @@ upnp_thread( void *aux )
while (r-- > 0) {
if ((ev[r].events & TVHPOLL_IN) != 0) {
- conn = (udp_connection_t *)ev[r].data.u64;
+ conn = ev[r].data.ptr;
iplen = sizeof(ip);
size = recvfrom(conn->fd, buf, sizeof(buf), 0,
(struct sockaddr *)&ip, &iplen);
@@ -145,7 +145,7 @@ upnp_thread( void *aux )
if (size > 0) {
char tbuf[256];
inet_ntop(ip.ss_family, IP_IN_ADDR(ip), tbuf, sizeof(tbuf));
- tvhtrace("upnp", "%s - received data from %s:%hu [size=%li]",
+ tvhtrace("upnp", "%s - received data from %s:%hu [size=%zi]",
conn == multicast ? "multicast" : "unicast",
tbuf, IP_PORT(ip), size);
tvhlog_hexdump("upnp", buf, size);
From dcb5ee367f50286dfbcd16db87fdbe28e794abd6 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Fri, 18 Apr 2014 17:05:11 +0200
Subject: [PATCH 28/42] SAT>IP : Fix satip_frontend_is_free..
---
src/input/mpegts/satip/satip_frontend.c | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 756908f7..190e31df 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -249,12 +249,8 @@ const idclass_t satip_frontend_dvbc_class =
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;
+ /* TODO: Add some RTSP live checks here */
+ return mpegts_input_is_free(mi);
}
static int
From 2878c4434def49f1f2b8d6bf0e34b3269a2e69a0 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Fri, 18 Apr 2014 21:55:33 +0200
Subject: [PATCH 29/42] SAT>IP: Move RTP/RTCP UDP bind to udp.c
---
src/input/mpegts/satip/satip_frontend.c | 77 ++++++++-----------------
src/udp.c | 38 ++++++++++++
src/udp.h | 5 ++
3 files changed, 67 insertions(+), 53 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 190e31df..5f68b73b 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -1058,8 +1058,6 @@ 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 */
@@ -1071,6 +1069,14 @@ satip_frontend_tune0
}
assert(LIST_FIRST(&lfe->mi_mux_active) == NULL);
+ if (udp_bind_double(&lfe->sf_rtp, &lfe->sf_rtcp,
+ "satip", "rtp", "rtpc",
+ lfe->sf_device->sd_info.myaddr, lfe->sf_udp_rtp_port,
+ NULL, SATIP_BUF_SIZE, 16384) < 0)
+ return SM_CODE_TUNING_FAILED;
+
+ lfe->sf_rtp_port = ntohs(IP_PORT(lfe->sf_rtp->ip));
+
assert(lfe->sf_pids == NULL);
assert(lfe->sf_pids_tuned == NULL);
lfe->sf_pids_count = 0;
@@ -1082,54 +1088,21 @@ satip_frontend_tune0
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);
+ tvhtrace("satip", "%s - local RTP port %i RTCP port %i",
+ lfe->mi_name,
+ ntohs(IP_PORT(lfe->sf_rtp->ip)),
+ ntohs(IP_PORT(lfe->sf_rtcp->ip)));
- if (!res) {
- lfe->sf_mmi = mmi;
+ 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);
+ 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);
+ gtimer_arm_ms(&lfe->sf_monitor_timer, satip_frontend_signal_cb, lfe, 250);
- lfe->sf_running = 1;
- }
-
- return res;
+ lfe->sf_running = 1;
+ return 0;
}
static int
@@ -1143,7 +1116,6 @@ satip_frontend_tune1
tvhdebug("satip", "%s - starting %s", buf1, buf2);
/* Tune */
- tvhtrace("satip", "%s - tuning", buf1);
return satip_frontend_tune0(lfe, mmi);
}
@@ -1254,9 +1226,10 @@ satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe )
/* 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)
+ if (lfe->ti_id.in_class == &satip_frontend_dvbs_class) {
+ satip_satconf_save(lfe, m);
htsmsg_delete_field(m, "networks");
+ }
htsmsg_delete_field(m, "fe_override");
/* Add to list */
@@ -1271,14 +1244,12 @@ satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe )
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);
+ mpegts_input_stop_all((mpegts_input_t*)lfe);
+ /* Stop timer */
gtimer_disarm(&lfe->sf_monitor_timer);
/* Remove from adapter */
diff --git a/src/udp.c b/src/udp.c
index 0e4a93a7..335e0f25 100644
--- a/src/udp.c
+++ b/src/udp.c
@@ -229,6 +229,44 @@ error:
return NULL;
}
+int
+udp_bind_double ( udp_connection_t **_u1, udp_connection_t **_u2,
+ const char *subsystem, const char *name1,
+ const char *name2, const char *host, int port,
+ const char *ifname, int rxsize1, int rxsize2 )
+{
+ udp_connection_t *u1 = NULL, *u2 = NULL;
+ udp_connection_t *ucs[10] = { NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL };
+ int pos = 0, i, port2;
+
+ memset(&ucs, 0, sizeof(ucs));
+ while (1) {
+ u1 = udp_bind(subsystem, name1, host, port, ifname, rxsize1);
+ if (u1 == NULL || u1 == UDP_FATAL_ERROR)
+ goto fail;
+ port2 = ntohs(IP_PORT(u1->ip));
+ /* RTP port should be even, RTCP port should be odd */
+ if ((port2 % 2) == 0) {
+ u2 = udp_bind(subsystem, name2, host, port2 + 1, ifname, rxsize2);
+ if (u2 != NULL && u2 != UDP_FATAL_ERROR)
+ break;
+ }
+ ucs[pos++] = u1;
+ if (port || pos >= ARRAY_SIZE(ucs))
+ goto fail;
+ }
+ for (i = 0; i < pos; i++)
+ udp_close(ucs[i]);
+ *_u1 = u1;
+ *_u2 = u2;
+ return 0;
+fail:
+ for (i = 0; i < pos; i++)
+ udp_close(ucs[i]);
+ return -1;
+}
+
udp_connection_t *
udp_connect ( const char *subsystem, const char *name,
const char *host, int port,
diff --git a/src/udp.h b/src/udp.h
index fe302e16..6f2fd904 100644
--- a/src/udp.h
+++ b/src/udp.h
@@ -52,6 +52,11 @@ udp_connection_t *
udp_bind ( const char *subsystem, const char *name,
const char *bindaddr, int port,
const char *ifname, int rxsize );
+int
+udp_bind_double ( udp_connection_t **_u1, udp_connection_t **_u2,
+ const char *subsystem, const char *name1,
+ const char *name2, const char *host, int port,
+ const char *ifname, int rxsize1, int rxsize2 );
udp_connection_t *
udp_connect ( const char *subsystem, const char *name,
const char *host, int port,
From 140b6983a5e5f004bdd40ee68dc48c304043a733 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Fri, 18 Apr 2014 22:55:15 +0200
Subject: [PATCH 30/42] SAT>IP: Fix the weight callback to not block multiple
tuners
---
src/input/mpegts/satip/satip_frontend.c | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 5f68b73b..19c53c8e 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -256,12 +256,7 @@ satip_frontend_is_free ( mpegts_input_t *mi )
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;
+ return mpegts_input_get_weight(mi);
}
static int
From a71b2e3001ab8e48dfc272a51467a81da2e87aec Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Fri, 18 Apr 2014 23:41:20 +0200
Subject: [PATCH 31/42] udp: create multirecv interface
---
src/input/mpegts/satip/satip_frontend.c | 84 +++-----------------
src/udp.c | 100 ++++++++++++++++++++++++
src/udp.h | 17 ++++
3 files changed, 129 insertions(+), 72 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 19c53c8e..430a0cfc 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -17,8 +17,6 @@
* along with this program. If not, see .
*/
-#define _GNU_SOURCE
-#include
#include
#include "tvheadend.h"
#include "tvhpoll.h"
@@ -26,57 +24,6 @@
#include "http.h"
#include "satip_private.h"
-#define PKTS 64
-
-#ifndef CONFIG_RECVMMSG
-
-#ifdef __linux__
-
-/* define the syscall - works only for linux */
-
-#include
-
-struct mmsghdr {
- struct msghdr msg_hdr;
- unsigned int msg_len;
-};
-
-int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
- unsigned int flags, struct timespec *timeout);
-
-#ifdef __NR_recvmmsg
-
-int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
- unsigned int flags, struct timespec *timeout)
-{
- return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout);
-}
-
-#else
-
-#undef PKTS
-#define PKTS 1
-/* receive only single packet */
-int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
- unsigned int flags, struct timespec *timeout)
-{
- ssize_t r = recvmsg(sockfd, &msgvec->msg_hdr, flags);
- if (r < 0)
- return r;
- msgvec->msg_len = r;
- return 1;
-}
-
-#endif
-
-#else /* not __linux__ */
-
-#error "Add recvmmsg() support for your platform!!!"
-
-#endif
-
-#endif /* !CONFIG_RECVMMSG */
-
static int
satip_frontend_tune1
( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi );
@@ -777,18 +724,18 @@ satip_frontend_pid_changed( http_client_t *rtsp,
static void *
satip_frontend_input_thread ( void *aux )
{
+#define RTP_PKTS 64
+#define RTP_PKT_SIZE 1472 /* this is maximum UDP payload (standard ethernet) */
#define HTTP_CMD_NONE 9874
satip_frontend_t *lfe = aux;
mpegts_mux_instance_t *mmi = lfe->sf_mmi;
http_client_t *rtsp;
dvb_mux_t *lm;
char buf[256];
- uint8_t tsb[PKTS][1356 + 128];
+ struct iovec *iovec;
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;
@@ -796,6 +743,7 @@ satip_frontend_input_thread ( void *aux )
tvhpoll_t *efd;
int changing = 0, ms = -1, fatal = 0;
uint32_t seq = -1, nseq;
+ udp_multirecv_t um;
lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf));
@@ -827,16 +775,6 @@ satip_frontend_input_thread ( void *aux )
tvhpoll_add(efd, ev, 4);
rtsp->hc_efd = efd;
- /* 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,
@@ -846,6 +784,7 @@ satip_frontend_input_thread ( void *aux )
return NULL;
}
+ udp_multirecv_init(&um, RTP_PKTS, RTP_PKT_SIZE);
sbuf_init_fixed(&sb, 18800);
while (tvheadend_running && !fatal) {
@@ -853,8 +792,8 @@ satip_frontend_input_thread ( void *aux )
nfds = tvhpoll_wait(efd, ev, 1, ms);
if (nfds > 0 && ev[0].data.ptr == NULL) {
- c = read(lfe->sf_dvr_pipe.rd, tsb[0], 1);
- if (c == 1 && tsb[0][0] == 'c') {
+ c = read(lfe->sf_dvr_pipe.rd, rtcp, 1);
+ if (c == 1 && rtcp[0] == 'c') {
ms = 20;
changing = 1;
continue;
@@ -932,7 +871,7 @@ satip_frontend_input_thread ( void *aux )
if (ev[0].data.ptr != lfe->sf_rtp)
continue;
- tc = recvmmsg(lfe->sf_rtp->fd, msg, PKTS, MSG_DONTWAIT, NULL);
+ tc = udp_multirecv_read(&um, lfe->sf_rtp->fd, RTP_PKTS, &iovec);
if (tc < 0) {
if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
@@ -941,14 +880,14 @@ satip_frontend_input_thread ( void *aux )
tvhlog(LOG_WARNING, "satip", "%s - recvmsg() EOVERFLOW", buf);
continue;
}
- tvhlog(LOG_ERR, "satip", "%s - recv() error %d (%s)",
+ tvhlog(LOG_ERR, "satip", "%s - multirecv error %d (%s)",
buf, errno, strerror(errno));
break;
}
for (i = 0; i < tc; i++) {
- p = tsb[i];
- c = msg[i].msg_len;
+ p = iovec[i].iov_base;
+ c = iovec[i].iov_len;
/* Strip RTP header */
if (c < 12)
@@ -980,6 +919,7 @@ satip_frontend_input_thread ( void *aux )
}
sbuf_free(&sb);
+ udp_multirecv_free(&um);
ev[0].events = TVHPOLL_IN;
ev[0].fd = lfe->sf_rtp->fd;
diff --git a/src/udp.c b/src/udp.c
index 335e0f25..a0789f09 100644
--- a/src/udp.c
+++ b/src/udp.c
@@ -18,6 +18,7 @@
* along with this program. If not, see .
*/
+#define _GNU_SOURCE
#include "tvheadend.h"
#include "udp.h"
@@ -380,3 +381,102 @@ udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
q->hq_size = 0;
return r;
}
+
+/*
+ * UDP multi packet receive support
+ */
+
+#ifndef CONFIG_RECVMMSG
+
+#ifdef __linux__
+
+/* define the syscall - works only for linux */
+
+#include
+
+struct mmsghdr {
+ struct msghdr msg_hdr;
+ unsigned int msg_len;
+};
+
+int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+ unsigned int flags, struct timespec *timeout);
+
+#ifdef __NR_recvmmsg
+
+int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+ unsigned int flags, struct timespec *timeout)
+{
+ return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout);
+}
+
+#else
+
+#undef PKTS
+#define PKTS 1
+/* receive only single packet */
+int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+ unsigned int flags, struct timespec *timeout)
+{
+ ssize_t r = recvmsg(sockfd, &msgvec->msg_hdr, flags);
+ if (r < 0)
+ return r;
+ msgvec->msg_len = r;
+ return 1;
+}
+
+#endif
+
+#else /* not __linux__ */
+
+#error "Add recvmmsg() support for your platform!!!"
+
+#endif
+
+#endif /* !CONFIG_RECVMMSG */
+
+void
+udp_multirecv_init( udp_multirecv_t *um, int packets, int psize )
+{
+ int i;
+
+ um->um_psize = psize;
+ um->um_packets = packets;
+ um->um_data = malloc(packets * psize);
+ um->um_iovec = malloc(packets * sizeof(struct iovec));
+ um->um_riovec = malloc(packets * sizeof(struct iovec));
+ um->um_msg = calloc(packets, sizeof(struct mmsghdr));
+ for (i = 0; i < packets; i++) {
+ ((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iov = &um->um_iovec[i];
+ ((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iovlen = 1;
+ um->um_iovec[i].iov_base = /* follow thru */
+ um->um_riovec[i].iov_base = um->um_data + i * psize;
+ um->um_iovec[i].iov_len = psize;
+ }
+}
+
+void
+udp_multirecv_free( udp_multirecv_t *um )
+{
+ free(um->um_msg); um->um_msg = NULL;
+ free(um->um_iovec); um->um_iovec = NULL;
+ free(um->um_data); um->um_data = NULL;
+ um->um_psize = 0;
+ um->um_packets = 0;
+}
+
+int
+udp_multirecv_read( udp_multirecv_t *um, int fd, int packets,
+ struct iovec **iovec )
+{
+ int n, i;
+ if (packets > um->um_packets)
+ packets = um->um_packets;
+ n = recvmmsg(fd, (struct mmsghdr *)um->um_msg, packets, MSG_DONTWAIT, NULL);
+ if (n > 0) {
+ for (i = 0; i < n; i++)
+ um->um_riovec[i].iov_len = ((struct mmsghdr *)um->um_msg)[i].msg_len;
+ *iovec = um->um_riovec;
+ }
+ return n;
+}
diff --git a/src/udp.h b/src/udp.h
index 6f2fd904..c5fa2eef 100644
--- a/src/udp.h
+++ b/src/udp.h
@@ -70,5 +70,22 @@ int
udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
struct sockaddr_storage *storage );
+typedef struct udp_multirecv {
+ int um_psize;
+ int um_packets;
+ uint8_t *um_data;
+ struct iovec *um_iovec;
+ struct iovec *um_riovec;
+ struct mmsghdr *um_msg;
+} udp_multirecv_t;
+
+void
+udp_multirecv_init( udp_multirecv_t *um, int packets, int psize );
+void
+udp_multirecv_free( udp_multirecv_t *um );
+int
+udp_multirecv_read( udp_multirecv_t *um, int fd, int packets,
+ struct iovec **iovec );
+
#endif /* UDP_H_ */
From b8f2fa84618ab2db4ac56d4e51bcb056211354ee Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Sat, 19 Apr 2014 19:19:16 +0200
Subject: [PATCH 32/42] SAT>IP: Fix bad memory access (wrong char array
dimension)
---
src/input/mpegts/satip/satip_rtsp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
index ed0bc831..f02de569 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -55,7 +55,7 @@ satip_rtsp_setup_find(const char *prefix, tvh2satip_t *tbl,
static void
satip_rtsp_add_val(const char *name, char *buf, uint32_t val)
{
- char sec[4];
+ char sec[5];
sprintf(buf + strlen(buf), "&%s=%i", name, val / 1000);
if (val % 1000) {
From cfe878319e7e348de11642d673e94b513303b3e1 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Sat, 19 Apr 2014 22:52:50 +0200
Subject: [PATCH 33/42] SAT>IP: Add possibility to define master/slave tuners
The signal from the standard universal LNB can be split using
a simple coaxial splitter (no multiswitch) to several outputs.
In this case, the position, the polarization and low-high band
settings must be equal.
This code adds the master/slave configuration option in the
tuner settings and does the tuner arbitration to preserve
above settings.
Ideally, this arbitration and configuration may be moved to
the generic dvbs input class in future, because all dvb-s
adapters can be wired in this way, but it's more complicated.
---
src/input/mpegts.h | 2 +-
src/input/mpegts/linuxdvb/linuxdvb_adapter.c | 2 +-
src/input/mpegts/linuxdvb/linuxdvb_frontend.c | 2 +-
src/input/mpegts/mpegts_input.c | 2 +-
src/input/mpegts/mpegts_mux.c | 4 +-
src/input/mpegts/mpegts_mux_dvb.c | 2 +-
src/input/mpegts/mpegts_service.c | 2 +-
src/input/mpegts/satip/satip_frontend.c | 178 +++++++++++++++++-
src/input/mpegts/satip/satip_private.h | 1 +
src/input/mpegts/satip/satip_satconf.c | 11 +-
10 files changed, 188 insertions(+), 18 deletions(-)
diff --git a/src/input/mpegts.h b/src/input/mpegts.h
index 24068b98..85493faa 100644
--- a/src/input/mpegts.h
+++ b/src/input/mpegts.h
@@ -505,7 +505,7 @@ struct mpegts_input
/*
* Functions
*/
- int (*mi_is_enabled) (mpegts_input_t*);
+ int (*mi_is_enabled) (mpegts_input_t*, mpegts_mux_t *mm);
void (*mi_enabled_updated)(mpegts_input_t*);
void (*mi_display_name) (mpegts_input_t*, char *buf, size_t len);
int (*mi_is_free) (mpegts_input_t*);
diff --git a/src/input/mpegts/linuxdvb/linuxdvb_adapter.c b/src/input/mpegts/linuxdvb/linuxdvb_adapter.c
index 74ba3c26..1ee9ba8e 100644
--- a/src/input/mpegts/linuxdvb/linuxdvb_adapter.c
+++ b/src/input/mpegts/linuxdvb/linuxdvb_adapter.c
@@ -119,7 +119,7 @@ linuxdvb_adapter_is_enabled ( linuxdvb_adapter_t *la )
{
linuxdvb_frontend_t *lfe;
LIST_FOREACH(lfe, &la->la_frontends, lfe_link) {
- if (lfe->mi_is_enabled((mpegts_input_t*)lfe))
+ if (lfe->mi_is_enabled((mpegts_input_t*)lfe, NULL))
return 1;
}
return 0;
diff --git a/src/input/mpegts/linuxdvb/linuxdvb_frontend.c b/src/input/mpegts/linuxdvb/linuxdvb_frontend.c
index 9cf81aa1..3d1d2f89 100644
--- a/src/input/mpegts/linuxdvb/linuxdvb_frontend.c
+++ b/src/input/mpegts/linuxdvb/linuxdvb_frontend.c
@@ -252,7 +252,7 @@ linuxdvb_frontend_get_grace ( mpegts_input_t *mi, mpegts_mux_t *mm )
}
static int
-linuxdvb_frontend_is_enabled ( mpegts_input_t *mi )
+linuxdvb_frontend_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm )
{
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
if (lfe->lfe_fe_path == NULL) return 0;
diff --git a/src/input/mpegts/mpegts_input.c b/src/input/mpegts/mpegts_input.c
index 2b3cd990..8a14cc84 100644
--- a/src/input/mpegts/mpegts_input.c
+++ b/src/input/mpegts/mpegts_input.c
@@ -162,7 +162,7 @@ const idclass_t mpegts_input_class =
* *************************************************************************/
static int
-mpegts_input_is_enabled ( mpegts_input_t *mi )
+mpegts_input_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm )
{
return mi->mi_enabled;
}
diff --git a/src/input/mpegts/mpegts_mux.c b/src/input/mpegts/mpegts_mux.c
index a19ba78b..a1c96d25 100644
--- a/src/input/mpegts/mpegts_mux.c
+++ b/src/input/mpegts/mpegts_mux.c
@@ -440,7 +440,7 @@ mpegts_mux_start
/* First pass - free only */
if (!pass) {
- int e = mmi->mmi_input->mi_is_enabled(mmi->mmi_input);
+ int e = mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mm);
int f = mmi->mmi_input->mi_is_free(mmi->mmi_input);
tvhtrace("mpegts", "%s - enabled %d free %d", buf, e, f);
if (e) enabled = 1;
@@ -457,7 +457,7 @@ mpegts_mux_start
} else {
/* Enabled, valid and lower weight */
- if (mmi->mmi_input->mi_is_enabled(mmi->mmi_input) &&
+ if (mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mm) &&
!mmi->mmi_tune_failed &&
(weight > mmi->mmi_input->mi_get_weight(mmi->mmi_input))) {
tune = mmi;
diff --git a/src/input/mpegts/mpegts_mux_dvb.c b/src/input/mpegts/mpegts_mux_dvb.c
index 094dd7e3..29d79927 100644
--- a/src/input/mpegts/mpegts_mux_dvb.c
+++ b/src/input/mpegts/mpegts_mux_dvb.c
@@ -592,7 +592,7 @@ dvb_mux_create_instances ( mpegts_mux_t *mm )
mpegts_network_link_t *mnl;
LIST_FOREACH(mnl, &mm->mm_network->mn_inputs, mnl_mn_link) {
mpegts_input_t *mi = mnl->mnl_input;
- if (mi->mi_is_enabled(mi))
+ if (mi->mi_is_enabled(mi, mm))
mi->mi_create_mux_instance(mi, mm);
}
}
diff --git a/src/input/mpegts/mpegts_service.c b/src/input/mpegts/mpegts_service.c
index 7a7dd880..15f4a9a2 100644
--- a/src/input/mpegts/mpegts_service.c
+++ b/src/input/mpegts/mpegts_service.c
@@ -189,7 +189,7 @@ mpegts_service_enlist(service_t *t, struct service_instance_list *sil)
if (mmi->mmi_tune_failed)
continue;
- if (!mmi->mmi_input->mi_is_enabled(mmi->mmi_input)) continue;
+ if (!mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mmi->mmi_mux)) continue;
/* Set weight to -1 (forced) for already active mux */
if (mmi->mmi_mux->mm_active == mmi) {
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 430a0cfc..fb894768 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -28,6 +28,20 @@ static int
satip_frontend_tune1
( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi );
+/*
+ *
+ */
+static satip_frontend_t *
+satip_frontend_find_by_number( satip_device_t *sd, int num )
+{
+ satip_frontend_t *lfe;
+
+ TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link)
+ if (lfe->sf_number == num)
+ return lfe;
+ return NULL;
+}
+
/* **************************************************************************
* Class definition
* *************************************************************************/
@@ -147,6 +161,46 @@ satip_frontend_dvbs_class_positions_set ( void *self, const void *val )
return 0;
}
+static int
+satip_frontend_dvbs_class_master_set ( void *self, const void *val )
+{
+ satip_frontend_t *lfe = self;
+ satip_frontend_t *lfe2 = NULL;
+ int num = *(int *)val, pos = 0;
+
+ if (num) {
+ TAILQ_FOREACH(lfe2, &lfe->sf_device->sd_frontends, sf_link)
+ if (lfe2 != lfe && lfe2->sf_type == lfe->sf_type)
+ if (++pos == num) {
+ num = lfe2->sf_number;
+ break;
+ }
+ }
+ if (lfe2 == NULL)
+ num = 0;
+ else if (lfe2->sf_master)
+ num = lfe2->sf_master;
+ if (lfe->sf_master != num) {
+ lfe->sf_master = num;
+ satip_device_destroy_later(lfe->sf_device, 100);
+ return 1;
+ }
+ return 0;
+}
+
+static htsmsg_t *
+satip_frontend_dvbs_class_master_enum( void * self )
+{
+ satip_frontend_t *lfe = self, *lfe2;
+ satip_device_t *sd = lfe->sf_device;
+ htsmsg_t *m = htsmsg_create_list();
+ htsmsg_add_str(m, NULL, "This Tuner");
+ TAILQ_FOREACH(lfe2, &sd->sd_frontends, sf_link)
+ if (lfe2 != lfe && lfe2->sf_type == lfe->sf_type)
+ htsmsg_add_str(m, NULL, lfe2->mi_name);
+ return m;
+}
+
const idclass_t satip_frontend_dvbs_class =
{
.ic_super = &satip_frontend_class,
@@ -163,6 +217,36 @@ const idclass_t satip_frontend_dvbs_class =
.off = offsetof(satip_frontend_t, sf_positions),
.def.i = 4
},
+ {
+ .type = PT_INT,
+ .id = "fe_master",
+ .name = "Master Tuner",
+ .set = satip_frontend_dvbs_class_master_set,
+ .list = satip_frontend_dvbs_class_master_enum,
+ .off = offsetof(satip_frontend_t, sf_master),
+ },
+ {
+ .id = "networks",
+ .type = PT_NONE,
+ },
+ {}
+ }
+};
+
+const idclass_t satip_frontend_dvbs_slave_class =
+{
+ .ic_super = &satip_frontend_class,
+ .ic_class = "satip_frontend_dvbs",
+ .ic_caption = "SAT>IP DVB-S Slave Frontend",
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_INT,
+ .id = "fe_master",
+ .name = "Master Tuner",
+ .set = satip_frontend_dvbs_class_master_set,
+ .list = satip_frontend_dvbs_class_master_enum,
+ .off = offsetof(satip_frontend_t, sf_master),
+ },
{
.id = "networks",
.type = PT_NONE,
@@ -227,10 +311,56 @@ satip_frontend_get_grace ( mpegts_input_t *mi, mpegts_mux_t *mm )
}
static int
-satip_frontend_is_enabled ( mpegts_input_t *mi )
+satip_frontend_match_satcfg ( satip_frontend_t *lfe2, mpegts_mux_t *mm2 )
+{
+ mpegts_mux_t *mm1;
+ dvb_mux_conf_t *mc1, *mc2;
+ int position, high1, high2;
+
+ if (lfe2->sf_mmi == NULL)
+ return 0;
+ mm1 = lfe2->sf_mmi->mmi_mux;
+ position = satip_satconf_get_position(lfe2, mm2);
+ if (lfe2->sf_position != position)
+ return 0;
+ mc1 = &((dvb_mux_t *)mm1)->lm_tuning;
+ mc2 = &((dvb_mux_t *)mm2)->lm_tuning;
+ if (mc1->dmc_fe_type != DVB_TYPE_S || mc2->dmc_fe_type != DVB_TYPE_S)
+ return 0;
+ if (mc1->u.dmc_fe_qpsk.polarisation != mc2->u.dmc_fe_qpsk.polarisation)
+ return 0;
+ high1 = mc1->dmc_fe_freq > 11700000;
+ high2 = mc2->dmc_fe_freq > 11700000;
+ if (high1 != high2)
+ return 0;
+ return 1;
+}
+
+static int
+satip_frontend_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm )
{
satip_frontend_t *lfe = (satip_frontend_t*)mi;
+ satip_frontend_t *lfe2;
+
+ lock_assert(&global_lock);
+
if (!lfe->mi_enabled) return 0;
+ if (lfe->sf_type != DVB_TYPE_S) return 1;
+ /* check if any "blocking" tuner is running */
+ TAILQ_FOREACH(lfe2, &lfe->sf_device->sd_frontends, sf_link) {
+ if (lfe2 == lfe) continue;
+ if (lfe2->sf_type != DVB_TYPE_S) continue;
+ if (lfe->sf_master == lfe2->sf_number) {
+ if (!lfe2->sf_running)
+ return 0; /* master must be running */
+ return satip_frontend_match_satcfg(lfe2, mm);
+ }
+ if (lfe2->sf_master == lfe->sf_number && lfe2->sf_running) {
+ if (lfe2->sf_mmi == NULL)
+ return 0;
+ return satip_frontend_match_satcfg(lfe2, mm);
+ }
+ }
return 1;
}
@@ -727,7 +857,7 @@ satip_frontend_input_thread ( void *aux )
#define RTP_PKTS 64
#define RTP_PKT_SIZE 1472 /* this is maximum UDP payload (standard ethernet) */
#define HTTP_CMD_NONE 9874
- satip_frontend_t *lfe = aux;
+ satip_frontend_t *lfe = aux, *lfe2;
mpegts_mux_instance_t *mmi = lfe->sf_mmi;
http_client_t *rtsp;
dvb_mux_t *lm;
@@ -775,8 +905,14 @@ satip_frontend_input_thread ( void *aux )
tvhpoll_add(efd, ev, 4);
rtsp->hc_efd = efd;
+ pos = lfe->sf_position;
+ if (lfe->sf_master) {
+ lfe2 = satip_frontend_find_by_number(lfe->sf_device, lfe->sf_master);
+ if (lfe2)
+ pos = lfe2->sf_position;
+ }
r = satip_rtsp_setup(rtsp,
- lfe->sf_position, lfe->sf_number,
+ pos, lfe->sf_number,
lfe->sf_rtp_port, &lm->lm_tuning,
lfe->sf_device->sd_pids0);
if (r < 0) {
@@ -1064,8 +1200,9 @@ satip_frontend_create
{
const idclass_t *idc;
const char *uuid = NULL, *override = NULL;
- char id[12], lname[256];
+ char id[16], lname[256];
satip_frontend_t *lfe;
+ uint32_t master = 0;
int i;
/* Override type */
@@ -1080,6 +1217,14 @@ satip_frontend_create
override = NULL;
}
}
+ /* Tuner slave */
+ snprintf(id, sizeof(id), "master for #%d", num);
+ if (conf && type == DVB_TYPE_S) {
+ if (htsmsg_get_u32(conf, id, &master))
+ master = 0;
+ if (master == num)
+ master = 0;
+ }
/* Internal config ID */
snprintf(id, sizeof(id), "%s #%d", dvb_type2str(type), num);
if (conf)
@@ -1089,7 +1234,8 @@ satip_frontend_create
/* Class */
if (type == DVB_TYPE_S)
- idc = &satip_frontend_dvbs_class;
+ idc = master ? &satip_frontend_dvbs_slave_class :
+ &satip_frontend_dvbs_class;
else if (type == DVB_TYPE_T)
idc = &satip_frontend_dvbt_class;
else if (type == DVB_TYPE_C)
@@ -1107,6 +1253,7 @@ satip_frontend_create
lfe->sf_number = num;
lfe->sf_type = type;
lfe->sf_type_t2 = t2;
+ lfe->sf_master = master;
lfe->sf_type_override = override ? strdup(override) : NULL;
TAILQ_INIT(&lfe->sf_satconf);
pthread_mutex_init(&lfe->sf_dvr_lock, NULL);
@@ -1146,16 +1293,28 @@ satip_frontend_create
TAILQ_INSERT_TAIL(&sd->sd_frontends, lfe, sf_link);
/* Create satconf */
- if (lfe->sf_type == DVB_TYPE_S)
+ if (lfe->sf_type == DVB_TYPE_S && master == 0)
satip_satconf_create(lfe, conf);
+ /* Slave networks update */
+ if (master) {
+ satip_frontend_t *lfe2 = satip_frontend_find_by_number(sd, master);
+ if (lfe2) {
+ htsmsg_t *l = (htsmsg_t *)mpegts_input_class_network_get(lfe2);
+ if (l) {
+ mpegts_input_class_network_set(lfe, l);
+ htsmsg_destroy(l);
+ }
+ }
+ }
+
return lfe;
}
void
satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe )
{
- char id[12];
+ char id[16];
htsmsg_t *m = htsmsg_create_map();
/* Save frontend */
@@ -1166,6 +1325,7 @@ satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe )
htsmsg_delete_field(m, "networks");
}
htsmsg_delete_field(m, "fe_override");
+ htsmsg_delete_field(m, "fe_master");
/* Add to list */
snprintf(id, sizeof(id), "%s #%d", dvb_type2str(lfe->sf_type), lfe->sf_number);
@@ -1174,6 +1334,10 @@ satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe )
snprintf(id, sizeof(id), "override #%d", lfe->sf_number);
htsmsg_add_str(fe, id, lfe->sf_type_override);
}
+ if (lfe->sf_master) {
+ snprintf(id, sizeof(id), "master for #%d", lfe->sf_number);
+ htsmsg_add_u32(fe, id, lfe->sf_master);
+ }
}
void
diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h
index a1051c2b..17be60fd 100644
--- a/src/input/mpegts/satip/satip_private.h
+++ b/src/input/mpegts/satip/satip_private.h
@@ -98,6 +98,7 @@ struct satip_frontend
dvb_fe_type_t sf_type;
int sf_type_t2;
char *sf_type_override;
+ int sf_master;
int sf_udp_rtp_port;
int sf_fullmux;
diff --git a/src/input/mpegts/satip/satip_satconf.c b/src/input/mpegts/satip/satip_satconf.c
index 20b47c68..5e2a2b08 100644
--- a/src/input/mpegts/satip/satip_satconf.c
+++ b/src/input/mpegts/satip/satip_satconf.c
@@ -41,7 +41,7 @@ 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;
+ return sfc ? sfc->sfc_priority : 0;
}
int
@@ -49,7 +49,7 @@ 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;
+ return sfc ? sfc->sfc_position : 0;
}
/* **************************************************************************
@@ -94,7 +94,7 @@ satip_satconf_class_network_set( void *o, const void *p )
sfc->sfc_networks = n;
/* update the input (frontend) network list */
htsmsg_t *l = htsmsg_create_list();
- satip_frontend_t *lfe = sfc->sfc_lfe;
+ satip_frontend_t *lfe = sfc->sfc_lfe, *lfe2;
satip_satconf_t *sfc2;
TAILQ_FOREACH(sfc2, &lfe->sf_satconf, sfc_link) {
for (i = 0; i < sfc2->sfc_networks->is_count; i++)
@@ -102,6 +102,11 @@ satip_satconf_class_network_set( void *o, const void *p )
idnode_uuid_as_str(sfc2->sfc_networks->is_array[i]));
}
mpegts_input_class_network_set(lfe, l);
+ /* update the slave tuners, too */
+ TAILQ_FOREACH(lfe2, &lfe->sf_device->sd_frontends, sf_link)
+ if (lfe2->sf_number != lfe->sf_number &&
+ lfe2->sf_master == lfe->sf_number)
+ mpegts_input_class_network_set(lfe2, l);
htsmsg_destroy(l);
} else {
idnode_set_free(n);
From 14f6c1f7bcfcadde09b1d344add36273067ec99d Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Sun, 20 Apr 2014 13:10:59 +0200
Subject: [PATCH 34/42] SAT>IP: DVB-S setup - pass only configured parameters
(fec, rolloff, pilot)
---
src/input/mpegts/satip/satip_rtsp.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
index f02de569..280ea024 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -162,11 +162,18 @@ satip_rtsp_setup( http_client_t *hc, int src, int fe,
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");
+ if (dmc->dmc_fe_modulation != DVB_MOD_NONE &&
+ dmc->dmc_fe_modulation != DVB_MOD_AUTO)
+ 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)
+ if (dmc->u.dmc_fe_qpsk.fec_inner != DVB_FEC_NONE &&
+ dmc->u.dmc_fe_qpsk.fec_inner != DVB_FEC_AUTO)
+ ADD(u.dmc_fe_qpsk.fec_inner, fec, "auto");
+ if (dmc->dmc_fe_rolloff != DVB_ROLLOFF_NONE &&
+ dmc->dmc_fe_rolloff != DVB_ROLLOFF_AUTO)
+ ADD(dmc_fe_rolloff, ro, "0.35");
+ if (dmc->dmc_fe_pilot != DVB_PILOT_NONE &&
+ dmc->dmc_fe_pilot != DVB_PILOT_AUTO)
ADD(dmc_fe_pilot, plts, "auto");
} else if (dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_A ||
dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_B ||
From 20880cc287a8828367eaf09470e623d1378cf4d5 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Sun, 20 Apr 2014 23:04:15 +0200
Subject: [PATCH 35/42] SAT>IP: Added octopusnet hack for DVB-S
---
src/input/mpegts/satip/satip_frontend.c | 52 +++++++++++++++++++++----
src/input/mpegts/satip/satip_private.h | 4 ++
src/input/mpegts/satip/satip_rtsp.c | 6 ++-
3 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index fb894768..9917c50b 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -112,6 +112,13 @@ const idclass_t satip_frontend_class =
.name = "UDP RTP Port Number (2 ports)",
.off = offsetof(satip_frontend_t, sf_udp_rtp_port),
},
+ {
+ .type = PT_BOOL,
+ .id = "play2",
+ .name = "Send full PLAY cmd",
+ .opts = PO_ADVANCED,
+ .off = offsetof(satip_frontend_t, sf_play2),
+ },
{}
}
};
@@ -874,6 +881,7 @@ satip_frontend_input_thread ( void *aux )
int changing = 0, ms = -1, fatal = 0;
uint32_t seq = -1, nseq;
udp_multirecv_t um;
+ int play2 = 1, position, rtsp_flags = 0;
lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf));
@@ -905,19 +913,21 @@ satip_frontend_input_thread ( void *aux )
tvhpoll_add(efd, ev, 4);
rtsp->hc_efd = efd;
- pos = lfe->sf_position;
+ position = lfe->sf_position;
if (lfe->sf_master) {
lfe2 = satip_frontend_find_by_number(lfe->sf_device, lfe->sf_master);
if (lfe2)
- pos = lfe2->sf_position;
+ position = lfe2->sf_position;
}
+ if (lfe->sf_device->sd_pids0)
+ rtsp_flags |= SATIP_SETUP_PIDS0;
r = satip_rtsp_setup(rtsp,
- pos, lfe->sf_number,
+ position, lfe->sf_number,
lfe->sf_rtp_port, &lm->lm_tuning,
- lfe->sf_device->sd_pids0);
+ rtsp_flags);
if (r < 0) {
tvherror("satip", "%s - failed to tune", buf);
- return NULL;
+ goto done;
}
udp_multirecv_init(&um, RTP_PKTS, RTP_PKT_SIZE);
@@ -977,9 +987,26 @@ satip_frontend_input_thread ( void *aux )
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);
+ if (lfe->sf_play2) {
+ r = satip_rtsp_setup(rtsp, position, lfe->sf_number,
+ lfe->sf_rtp_port, &lm->lm_tuning,
+ rtsp_flags | SATIP_SETUP_PLAY);
+ if (r < 0) {
+ tvherror("satip", "%s - failed to tune2", buf);
+ fatal = 1;
+ }
+ continue;
+ } else {
+ satip_frontend_pid_changed(rtsp, lfe, buf);
+ }
}
break;
+ case RTSP_CMD_PLAY:
+ if (rtsp->hc_code == 200 && play2) {
+ satip_frontend_pid_changed(rtsp, lfe, buf);
+ play2 = 0;
+ }
+ /* fall thru */
default:
if (rtsp->hc_code >= 400) {
tvhlog(LOG_ERR, "satip", "%s - RTSP cmd error %d (%s) [%i-%i]",
@@ -1087,8 +1114,9 @@ satip_frontend_input_thread ( void *aux )
}
}
}
- http_client_close(rtsp);
+done:
+ http_client_close(rtsp);
tvhpoll_destroy(efd);
return NULL;
#undef PKTS
@@ -1194,6 +1222,15 @@ satip_frontend_tune1
* Creation/Config
* *************************************************************************/
+static void
+satip_frontend_hacks( satip_frontend_t *lfe )
+{
+ if (strstr(lfe->sf_device->sd_info.location, ":8888/octonet.xml")) {
+ if (lfe->sf_type == DVB_TYPE_S)
+ lfe->sf_play2 = 1;
+ }
+}
+
satip_frontend_t *
satip_frontend_create
( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num )
@@ -1255,6 +1292,7 @@ satip_frontend_create
lfe->sf_type_t2 = t2;
lfe->sf_master = master;
lfe->sf_type_override = override ? strdup(override) : NULL;
+ satip_frontend_hacks(lfe);
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);
diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h
index 17be60fd..32285336 100644
--- a/src/input/mpegts/satip/satip_private.h
+++ b/src/input/mpegts/satip/satip_private.h
@@ -101,6 +101,7 @@ struct satip_frontend
int sf_master;
int sf_udp_rtp_port;
int sf_fullmux;
+ int sf_play2;
/*
* Reception
@@ -201,6 +202,9 @@ int satip_satconf_get_position
* RTSP part
*/
+#define SATIP_SETUP_PLAY (1<<0)
+#define SATIP_SETUP_PIDS0 (1<<1)
+
int
satip_rtsp_setup( http_client_t *hc,
int src, int fe, int udp_port,
diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c
index 280ea024..19d2ee8f 100644
--- a/src/input/mpegts/satip/satip_rtsp.c
+++ b/src/input/mpegts/satip/satip_rtsp.c
@@ -70,7 +70,7 @@ satip_rtsp_add_val(const char *name, char *buf, uint32_t val)
int
satip_rtsp_setup( http_client_t *hc, int src, int fe,
- int udp_port, const dvb_mux_conf_t *dmc, int pids0 )
+ int udp_port, const dvb_mux_conf_t *dmc, int flags )
{
static tvh2satip_t msys[] = {
{ .t = DVB_SYS_DVBT, "dvbt" },
@@ -202,12 +202,14 @@ satip_rtsp_setup( http_client_t *hc, int src, int fe,
dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_NONE)
ADD(u.dmc_fe_ofdm.guard_interval, gi, "18");
}
- if (pids0)
+ if (flags & SATIP_SETUP_PIDS0)
strcat(buf, "&pids=0");
tvhtrace("satip", "setup params - %s", buf);
if (hc->hc_rtsp_stream_id >= 0)
snprintf(stream = _stream, sizeof(_stream), "/stream=%li",
hc->hc_rtsp_stream_id);
+ if (flags & SATIP_SETUP_PLAY)
+ return rtsp_play(hc, stream, buf);
return rtsp_setup(hc, stream, buf, NULL, udp_port, udp_port + 1);
}
From 08e0e4ebfd7c86ed0bda5d542e496e4df2dc4ec6 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Tue, 22 Apr 2014 11:08:43 +0200
Subject: [PATCH 36/42] SATIP: Try to improve the uncorrectable error counter
---
src/input/mpegts/satip/satip_frontend.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 9917c50b..2732c5b3 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -1072,7 +1072,8 @@ satip_frontend_input_thread ( void *aux )
if (seq == -1)
seq = nseq;
else if (((seq + 1) & 0xffff) != nseq)
- mmi->mmi_stats.unc++;
+ mmi->mmi_stats.unc += ((c - pos) / 188) *
+ (uint32_t)((uint16_t)nseq-(uint16_t)(seq+1));
seq = nseq;
/* Process */
sbuf_append(&sb, p + pos, c - pos);
From 31977de37a3da1ef7c0ed4a8b95eebb6e3d8825f Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Tue, 22 Apr 2014 15:20:24 +0200
Subject: [PATCH 37/42] SAT>IP: Fix the forced full mux per tuner
---
src/input/mpegts/satip/satip_frontend.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index 2732c5b3..f973a657 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -750,6 +750,8 @@ satip_frontend_pid_changed( http_client_t *rtsp,
if (lfe->sf_pids_count > lfe->sf_device->sd_pids_max)
any = lfe->sf_device->sd_fullmux_ok ? 1 : 0;
+ if (lfe->sf_fullmux && lfe->sf_device->sd_fullmux_ok)
+ any = 1;
if (any) {
From 21ad991222bd81a2ed9156a14742452acc3661f8 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Tue, 22 Apr 2014 17:18:25 +0200
Subject: [PATCH 38/42] udp + SAT>IP: better multipacket handling
- improve the recvmmsg() user space implementation
- some changes which may improve operation under high-loads
---
src/input/mpegts/satip/satip_frontend.c | 9 +++--
src/udp.c | 53 +++++++++++++------------
2 files changed, 33 insertions(+), 29 deletions(-)
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index f973a657..fdca0d6b 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -864,7 +864,8 @@ static void *
satip_frontend_input_thread ( void *aux )
{
#define RTP_PKTS 64
-#define RTP_PKT_SIZE 1472 /* this is maximum UDP payload (standard ethernet) */
+#define UDP_PKT_SIZE 1472 /* this is maximum UDP payload (standard ethernet) */
+#define RTP_PKT_SIZE (UDP_PKT_SIZE - 12) /* minus RTP minimal RTP header */
#define HTTP_CMD_NONE 9874
satip_frontend_t *lfe = aux, *lfe2;
mpegts_mux_instance_t *mmi = lfe->sf_mmi;
@@ -933,7 +934,7 @@ satip_frontend_input_thread ( void *aux )
}
udp_multirecv_init(&um, RTP_PKTS, RTP_PKT_SIZE);
- sbuf_init_fixed(&sb, 18800);
+ sbuf_init_fixed(&sb, RTP_PKTS * RTP_PKT_SIZE);
while (tvheadend_running && !fatal) {
@@ -1079,9 +1080,9 @@ satip_frontend_input_thread ( void *aux )
seq = nseq;
/* Process */
sbuf_append(&sb, p + pos, c - pos);
- mpegts_input_recv_packets((mpegts_input_t*)lfe, mmi,
- &sb, 0, NULL, NULL);
}
+ mpegts_input_recv_packets((mpegts_input_t*)lfe, mmi,
+ &sb, 0, NULL, NULL);
}
sbuf_free(&sb);
diff --git a/src/udp.c b/src/udp.c
index a0789f09..35ec2fcf 100644
--- a/src/udp.c
+++ b/src/udp.c
@@ -386,13 +386,10 @@ udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
* UDP multi packet receive support
*/
-#ifndef CONFIG_RECVMMSG
-
-#ifdef __linux__
-
+#if !defined (CONFIG_RECVMMSG) && defined(__linux__)
/* define the syscall - works only for linux */
-
#include
+#ifdef __NR_recvmmsg
struct mmsghdr {
struct msghdr msg_hdr;
@@ -402,38 +399,44 @@ struct mmsghdr {
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
unsigned int flags, struct timespec *timeout);
-#ifdef __NR_recvmmsg
-
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
unsigned int flags, struct timespec *timeout)
{
return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout);
}
-#else
+#define CONFIG_RECVMMSG
-#undef PKTS
-#define PKTS 1
-/* receive only single packet */
-int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
- unsigned int flags, struct timespec *timeout)
+#endif
+#endif
+
+
+#ifndef CONFIG_RECVMMSG
+
+struct mmsghdr {
+ struct msghdr msg_hdr;
+ unsigned int msg_len;
+};
+
+static int
+recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
+ unsigned int flags, struct timespec *timeout)
{
- ssize_t r = recvmsg(sockfd, &msgvec->msg_hdr, flags);
- if (r < 0)
- return r;
- msgvec->msg_len = r;
- return 1;
+ ssize_t r;
+ unsigned int i;
+
+ for (i = 0; i < vlen; i++) {
+ r = recvmsg(sockfd, &msgvec->msg_hdr, flags);
+ if (r < 0)
+ return (i > 0) ? i : r;
+ msgvec->msg_len = r;
+ msgvec++;
+ }
+ return i;
}
#endif
-#else /* not __linux__ */
-
-#error "Add recvmmsg() support for your platform!!!"
-
-#endif
-
-#endif /* !CONFIG_RECVMMSG */
void
udp_multirecv_init( udp_multirecv_t *um, int packets, int psize )
From 78fcbf5233a256e8ed509bfec864631ffc583845 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 23 Apr 2014 09:31:23 +0200
Subject: [PATCH 39/42] SAT>IP: Add Help button to the TV adapters.
---
docs/html/config_tvadapters.html | 81 ++++++++++++++++++++++++++++++
src/webui/static/app/idnode.js | 15 +++++-
src/webui/static/app/tvadapters.js | 9 +++-
3 files changed, 102 insertions(+), 3 deletions(-)
create mode 100644 docs/html/config_tvadapters.html
diff --git a/docs/html/config_tvadapters.html b/docs/html/config_tvadapters.html
new file mode 100644
index 00000000..9582a7e3
--- /dev/null
+++ b/docs/html/config_tvadapters.html
@@ -0,0 +1,81 @@
+
+
+
The adapters and tuners are listed / edited in a tree
+
+
+ - To edit an item, click on it. To commit edited changes back to
+ Tvheadend press the 'Save' button. In order to change a Checkbox cell
+ you only have to click once in it.
+
+
+
+
The rows have the following functions
+
+
+ - Enabled
+ - If selected, the IPTV service will be enabled an use for channel
+ subscriptions.
+
+ - Name
+ - The name of this tuner.
+
+ - Full Mux Rx mode
+ - If selected, the whole mux (transponder) will be received without any
+ filtering. It is not usually required to enable this option. It does
+ not have usually any benefit, because tvheadend will filter the
+ unwanted PIDs from the TS stream, except for the malfunctioning drivers.
+
+
+
+
LinuxDVB specific rows
+
+
+ - Keep FE open
+ - Enable to not close the LinuxDVB frontend device in the idle state.
+
+
+
SAT>IP specific rows
+
+
+ - Full Mux Rx mode supported
+ - Enable, if the SAT>IP box supports the full mux rx mode (pids=all)
+ parameter.
+
+ - Signal scale (240 or 100)
+ - Some SAT>IP boxes has only 0-100 scale. If your signal is too low, try
+ value 100 here.
+
+ - Maximum PIDs
-
+
- Maximum supported PIDs in the filter of the SAT>IP box.
+
+ - Maximum length of PIDs
-
+
- Maximum length in characters for the command setting PIDs to the
+ SAT>IP box.
+
+ - addpids/delpids supported
+ - Enable, if the SAT>IP box supports the addpids/delpids command.
+
+
- PIDs in setup
+ - Enable, if the SAT>IP box requires pids=0 parameter in the SETUP RTSP command.
+
+ - UDP RTP Port Number (2 ports)
+ - Force the local UDP Port number here. The number should be even (RTP port).
+ The next odd number (+1) will be used as the RTCP port.
+
+ - Satellite Positions
+ - Select the number of satellite positions supported by the SAT>IP
+ hardware and your coaxial cable wiring.
+
+ - Master Tuner
+ - Select the master tuner.
+
The signal from the standard universal LNB can be split using
+ a simple coaxial splitter (no multiswitch) to several outputs.
+ In this case, the position, the polarization and low-high
+ band settings must be equal.
+ If you set other tuner as master, then this tuner will act like
+ a slave one and tvheadend will assure that this tuner will not
+ use incompatible parameters (position, polarization, lo-hi).
+
+
+
+
diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js
index c172ff18..880565d6 100644
--- a/src/webui/static/app/idnode.js
+++ b/src/webui/static/app/idnode.js
@@ -458,6 +458,7 @@ tvheadend.idnode_editor_form = function ( d, panel )
tvheadend.idnode_editor = function(item, conf)
{
var panel = null;
+ var buttons = [];
/* Buttons */
var saveBtn = new Ext.Button({
@@ -476,6 +477,15 @@ tvheadend.idnode_editor = function(item, conf)
});
}
});
+ buttons.push(saveBtn);
+
+ if (conf.help) {
+ var helpBtn = new Ext.Button({
+ text : 'Help',
+ handler : conf.help
+ });
+ buttons.push(helpBtn);
+ }
panel = new Ext.FormPanel({
title : conf.title || null,
@@ -490,7 +500,7 @@ tvheadend.idnode_editor = function(item, conf)
//defaults: {width: 330},
defaultType : 'textfield',
buttonAlign : 'left',
- buttons : [ saveBtn ]
+ buttons : buttons
});
tvheadend.idnode_editor_form(item.props || item.params, panel);
@@ -1037,7 +1047,8 @@ tvheadend.idnode_tree = function (conf)
if(!n.isRoot)
current = panel.add(new tvheadend.idnode_editor(n.attributes, {
title : 'Parameters',
- fixedHeight : true
+ fixedHeight : true,
+ help : conf.help || null,
}));
panel.doLayout();
}
diff --git a/src/webui/static/app/tvadapters.js b/src/webui/static/app/tvadapters.js
index a15bb21b..d3353c09 100644
--- a/src/webui/static/app/tvadapters.js
+++ b/src/webui/static/app/tvadapters.js
@@ -1,3 +1,10 @@
tvheadend.tvadapters = function() {
- return tvheadend.idnode_tree({ url: 'api/hardware/tree', title: 'TV adapters', comet: 'hardware'});
+ return tvheadend.idnode_tree( {
+ url : 'api/hardware/tree',
+ title : 'TV adapters',
+ comet : 'hardware',
+ help : function() {
+ new tvheadend.help('TV adapters', 'config_tvadapters.html');
+ }
+ });
}
From 25adfaa1fac6c293c8597ed4537c894068ce589e Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 23 Apr 2014 13:54:30 +0200
Subject: [PATCH 40/42] SAT>IP: Remove the full mux option like in linuxdvb
---
docs/html/config_tvadapters.html | 6 ------
src/input/mpegts/satip/satip_frontend.c | 8 --------
src/input/mpegts/satip/satip_private.h | 1 -
3 files changed, 15 deletions(-)
diff --git a/docs/html/config_tvadapters.html b/docs/html/config_tvadapters.html
index 9582a7e3..a4d9a36f 100644
--- a/docs/html/config_tvadapters.html
+++ b/docs/html/config_tvadapters.html
@@ -18,12 +18,6 @@
Name
The name of this tuner.
-
- Full Mux Rx mode
- If selected, the whole mux (transponder) will be received without any
- filtering. It is not usually required to enable this option. It does
- not have usually any benefit, because tvheadend will filter the
- unwanted PIDs from the TS stream, except for the malfunctioning drivers.
diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c
index fdca0d6b..5c119389 100644
--- a/src/input/mpegts/satip/satip_frontend.c
+++ b/src/input/mpegts/satip/satip_frontend.c
@@ -100,12 +100,6 @@ const idclass_t satip_frontend_class =
.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",
@@ -750,8 +744,6 @@ satip_frontend_pid_changed( http_client_t *rtsp,
if (lfe->sf_pids_count > lfe->sf_device->sd_pids_max)
any = lfe->sf_device->sd_fullmux_ok ? 1 : 0;
- if (lfe->sf_fullmux && lfe->sf_device->sd_fullmux_ok)
- any = 1;
if (any) {
diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h
index 32285336..6423eb3b 100644
--- a/src/input/mpegts/satip/satip_private.h
+++ b/src/input/mpegts/satip/satip_private.h
@@ -100,7 +100,6 @@ struct satip_frontend
char *sf_type_override;
int sf_master;
int sf_udp_rtp_port;
- int sf_fullmux;
int sf_play2;
/*
From 2b37a0610e32fddf50a532e25935e9aede25210d Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Wed, 23 Apr 2014 20:55:28 +0200
Subject: [PATCH 41/42] SAT>IP: Add --satip_xml option to reach tuners behind
routers or local blocked UPnP ports
---
src/input/mpegts.c | 5 +-
src/input/mpegts.h | 3 +-
src/input/mpegts/satip/satip.c | 85 +++++++++++++++++++++++++++++-----
src/input/mpegts/satip/satip.h | 2 +-
src/main.c | 8 +++-
5 files changed, 87 insertions(+), 16 deletions(-)
diff --git a/src/input/mpegts.c b/src/input/mpegts.c
index 51117107..87d341a4 100644
--- a/src/input/mpegts.c
+++ b/src/input/mpegts.c
@@ -19,7 +19,8 @@
#include "input.h"
void
-mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners )
+mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
+ str_list_t *tsfiles, int tstuners )
{
/* Register classes (avoid API 400 errors due to not yet defined) */
idclass_register(&mpegts_network_class);
@@ -53,7 +54,7 @@ mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners )
/* SAT>IP DVB client */
#if ENABLE_SATIP_CLIENT
- satip_init();
+ satip_init(satip_client);
#endif
/* Mux schedulers */
diff --git a/src/input/mpegts.h b/src/input/mpegts.h
index 85493faa..ffc0fe1c 100644
--- a/src/input/mpegts.h
+++ b/src/input/mpegts.h
@@ -68,7 +68,8 @@ extern const idclass_t mpegts_input_class;
* Setup / Tear down
* *************************************************************************/
-void mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners );
+void mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
+ str_list_t *tsfiles, int tstuners );
void mpegts_done ( void );
/* **************************************************************************
diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c
index 9a238543..313ed747 100644
--- a/src/input/mpegts/satip/satip.c
+++ b/src/input/mpegts/satip/satip.c
@@ -390,6 +390,19 @@ satip_device_find( const char *satip_uuid )
return NULL;
}
+static satip_device_t *
+satip_device_find_by_descurl( const char *descurl )
+{
+ tvh_hardware_t *th;
+
+ TVH_HARDWARE_FOREACH(th) {
+ if (idnode_is_instance(&th->th_id, &satip_device_class) &&
+ strcmp(((satip_device_t *)th)->sd_info.location, descurl) == 0)
+ return (satip_device_t *)th;
+ }
+ return NULL;
+}
+
void
satip_device_save( satip_device_t *sd )
{
@@ -483,6 +496,7 @@ 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 str_list_t *satip_static_clients;
static void
satip_discovery_destroy(satip_discovery_t *d, int unlink)
@@ -525,10 +539,12 @@ satip_discovery_http_closed(http_client_t *hc, int errn)
htsmsg_t *xml = NULL, *tags, *root, *device;
const char *friendlyname, *manufacturer, *manufacturerURL, *modeldesc;
const char *modelname, *modelnum, *serialnum;
- const char *presentation, *tunercfg;
+ const char *presentation, *tunercfg, *udn, *uuid;
const char *cs;
satip_device_info_t info;
char errbuf[100];
+ char *argv[10];
+ int i, n;
s = http_arg_get(&hc->hc_args, "Content-Type");
if (s && strcasecmp(s, "text/xml")) {
@@ -590,11 +606,24 @@ satip_discovery_http_closed(http_client_t *hc, int errn)
goto finish;
if ((presentation = htsmsg_xml_get_cdata_str(device, "presentationURL")) == NULL)
goto finish;
+ if ((udn = htsmsg_xml_get_cdata_str(device, "UDN")) == NULL)
+ goto finish;
if ((tunercfg = htsmsg_xml_get_cdata_str(device, "urn:ses-com:satipX_SATIPCAP")) == NULL)
goto finish;
+
+ uuid = NULL;
+ n = http_tokenize((char *)udn, argv, ARRAY_SIZE(argv), ':');
+ for (i = 0; i < n+1; i++)
+ if (argv[i] && strcmp(argv[i], "uuid") == 0) {
+ uuid = argv[++i];
+ break;
+ }
+ if (uuid == NULL || (d->uuid[0] && strcmp(uuid, d->uuid)))
+ goto finish;
+
info.myaddr = strdup(d->myaddr);
info.addr = strdup(d->url.host);
- info.uuid = strdup(d->uuid);
+ info.uuid = strdup(uuid);
info.bootid = strdup(d->bootid);
info.configid = strdup(d->configid);
info.deviceid = strdup(d->deviceid);
@@ -732,7 +761,7 @@ satip_discovery_service_received
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, ':');
+ n = http_tokenize(argv[1], argv, ARRAY_SIZE(argv), ':');
for (i = 0; i < n+1; i++)
if (argv[i] && strcmp(argv[i], "uuid") == 0) {
uuid = argv[++i];
@@ -785,6 +814,32 @@ satip_discovery_service_received
satip_discovery_destroy(d, 0);
}
+static void
+satip_discovery_static(const char *descurl)
+{
+ satip_discovery_t *d;
+
+ lock_assert(&global_lock);
+
+ if (satip_device_find_by_descurl(descurl))
+ return;
+ d = calloc(1, sizeof(satip_discovery_t));
+ if (urlparse(descurl, &d->url)) {
+ satip_discovery_destroy(d, 0);
+ return;
+ }
+ d->myaddr = strdup(d->url.host);
+ d->location = strdup(descurl);
+ d->server = strdup("");
+ d->uuid = strdup("");
+ d->bootid = strdup("");
+ d->configid = strdup("");
+ d->deviceid = strdup("");
+ TAILQ_INSERT_TAIL(&satip_discoveries, d, disc_link);
+ satip_discoveries_count++;
+ satip_discovery_timerq_cb(NULL);
+}
+
static void
satip_discovery_service_destroy(upnp_service_t *us)
{
@@ -801,7 +856,7 @@ MAN: \"ssdp:discover\"\r\n\
MX: 2\r\n\
ST: urn:ses-com:device:SatIPServer:1\r\n\
\r\n"
- htsbuf_queue_t q;
+ int i;
if (!tvheadend_running)
return;
@@ -811,13 +866,20 @@ ST: urn:ses-com:device:SatIPServer:1\r\n\
}
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;
+ if (satip_discovery_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);
+ if (satip_discovery_service) {
+ htsbuf_queue_t q;
+ htsbuf_queue_init(&q, 0);
+ htsbuf_append(&q, MSG, sizeof(MSG)-1);
+ upnp_send(&q, NULL);
+ htsbuf_queue_flush(&q);
+ }
+ for (i = 0; i < satip_static_clients->num; i++)
+ satip_discovery_static(satip_static_clients->str[i]);
gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 3600);
#undef MSG
}
@@ -832,9 +894,10 @@ satip_device_discovery_start( void )
* Initialization
*/
-void satip_init ( void )
+void satip_init ( str_list_t *clients )
{
TAILQ_INIT(&satip_discoveries);
+ satip_static_clients = clients;
satip_device_discovery_start();
}
diff --git a/src/input/mpegts/satip/satip.h b/src/input/mpegts/satip/satip.h
index 6e994c82..59d035eb 100644
--- a/src/input/mpegts/satip/satip.h
+++ b/src/input/mpegts/satip/satip.h
@@ -20,7 +20,7 @@
#ifndef __TVH_SATIP_H__
#define __TVH_SATIP_H__
-void satip_init( void );
+void satip_init( str_list_t *clients );
void satip_done( void );
#endif /* __TVH_SATIP_H__ */
diff --git a/src/main.c b/src/main.c
index fba0b10c..8d4add02 100644
--- a/src/main.c
+++ b/src/main.c
@@ -476,6 +476,7 @@ main(int argc, char **argv)
#endif
*opt_bindaddr = NULL,
*opt_subscribe = NULL;
+ str_list_t opt_satip_xml = { .max = 10, .num = 0, .str = calloc(10, sizeof(char*)) };
str_list_t opt_tsfile = { .max = 10, .num = 0, .str = calloc(10, sizeof(char*)) };
cmdline_opt_t cmdline_opts[] = {
{ 0, NULL, "Generic Options", OPT_BOOL, NULL },
@@ -497,6 +498,10 @@ main(int argc, char **argv)
#if ENABLE_LINUXDVB
{ 'a', "adapters", "Only use specified DVB adapters (comma separated)",
OPT_STR, &opt_dvb_adapters },
+#endif
+#if ENABLE_SATIP_CLIENT
+ { 0, "satip_xml", "URL with the SAT>IP server XML location",
+ OPT_STR_LIST, &opt_satip_xml },
#endif
{ 0, NULL, "Server Connectivity", OPT_BOOL, NULL },
{ '6', "ipv6", "Listen on IPv6", OPT_BOOL, &opt_ipv6 },
@@ -766,7 +771,7 @@ main(int argc, char **argv)
service_init();
#if ENABLE_MPEGTS
- mpegts_init(adapter_mask, &opt_tsfile, opt_tsfile_tuner);
+ mpegts_init(adapter_mask, &opt_satip_xml, &opt_tsfile, opt_tsfile_tuner);
#endif
channel_init();
@@ -881,6 +886,7 @@ main(int argc, char **argv)
unlink(opt_pidpath);
free(opt_tsfile.str);
+ free(opt_satip_xml.str);
/* OpenSSL - welcome to the "cleanup" hell */
ENGINE_cleanup();
From 47b97e52a87726ed39c55ea1622aa876253ae32b Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela
Date: Fri, 7 Mar 2014 08:39:00 +0100
Subject: [PATCH 42/42] Add esfilter (elementary stream filter)
Some feeds (especially from satellite) includes many language mutations
and other elementary streams. It may be useful to define the filters and
order scheme for the streaming and DVR.
The service information dialog is extended to show all PIDs and filtered
PIDs to easy show the used PIDs.
See the included help file for more description.
---
Makefile | 4 +-
docs/html/config_esfilter.html | 109 +++
src/api.c | 1 +
src/api.h | 2 +
src/api/api_esfilter.c | 101 +++
src/api/api_idnode.c | 41 +-
src/api/api_service.c | 74 +-
src/esfilter.c | 1023 ++++++++++++++++++++++++++
src/esfilter.h | 110 +++
src/idnode.c | 28 +-
src/idnode.h | 17 +-
src/main.c | 3 +
src/service.c | 183 ++++-
src/service.h | 9 +-
src/subscriptions.c | 4 +-
src/tvheadend.h | 3 +
src/webui/extjs.c | 1 +
src/webui/static/app/esfilter.js | 108 +++
src/webui/static/app/ext.css | 13 +
src/webui/static/app/idnode.js | 68 +-
src/webui/static/app/mpegts.js | 54 +-
src/webui/static/app/tvheadend.js | 11 +
src/webui/static/icons/film_edit.png | 1 +
23 files changed, 1888 insertions(+), 80 deletions(-)
create mode 100644 docs/html/config_esfilter.html
create mode 100644 src/api/api_esfilter.c
create mode 100644 src/esfilter.c
create mode 100644 src/esfilter.h
create mode 100644 src/webui/static/app/esfilter.js
create mode 120000 src/webui/static/icons/film_edit.png
diff --git a/Makefile b/Makefile
index 6ec18b03..609a5a0b 100644
--- a/Makefile
+++ b/Makefile
@@ -117,6 +117,7 @@ SRCS = src/version.c \
src/rtsp.c \
src/fsmonitor.c \
src/cron.c \
+ src/esfilter.c
SRCS-${CONFIG_UPNP} += \
src/upnp.c
@@ -131,7 +132,8 @@ SRCS += \
src/api/api_mpegts.c \
src/api/api_epg.c \
src/api/api_epggrab.c \
- src/api/api_imagecache.c
+ src/api/api_imagecache.c \
+ src/api/api_esfilter.c
SRCS += \
src/parsers/parsers.c \
diff --git a/docs/html/config_esfilter.html b/docs/html/config_esfilter.html
new file mode 100644
index 00000000..75a03de4
--- /dev/null
+++ b/docs/html/config_esfilter.html
@@ -0,0 +1,109 @@
+
+
+This table defines rules to filter and order the elementary streams
+like video or audio from the input feed.
+
+
+The execution order of commands is granted. It means that first rule
+is executed for all available streams then second and so on.
+
+
+If any elementary stream is not marked as ignored or exclusive, it is
+used. If you like to ignore unknown elementary streams, add a rule
+to the end of grid with the any (not defined) comparisons and
+with the action ignore.
+
+
+The rules for different elementary stream groups (video, audio,
+teletext, subtitle, CA, other) are executed separately (as visually edited).
+
+
+For the visual verification of the filtering, there is a service info
+dialog in the Configuration / DVB Inputs / Services window . This dialog
+shows the received PIDs and filtered PIDs in one window.
+
+
+The rules are listed / edited in a grid.
+
+
+ - To edit a cell, double click on it. After a cell is changed it
+ will flags one of its corner to red to indicated that it has been
+ changed. To commit these changes back to Tvheadend press the
+ 'Save changes' button. In order to change a Checkbox cell you only
+ have to click once in it.
+
+
- To add a new entry, press the 'Add entry' button. The new (empty) entry
+ will be created on the server but will not be in its enabled state.
+ You can now change all the cells to the desired values, check the
+ 'enable' box and then press 'Save changes' to activate the new entry.
+
+
- To delete one or more entries, select the lines (by clicking once on
+ them), and press the 'Delete selected' button. A pop up
+ will ask you to confirm your request.
+
+
- To move up or down one or more entries, select the lines (by clicking
+ once on them), and press the 'Move up' or 'Move down' button.
+
+
+
+The columns have the following functions:
+
+
+ - Enabled
+
- If selected, the rule will be enabled.
+
+
- Stream Type
+
- Select the elementary stream type to compare. Empty field means any.
+
+
- Language
+
- Select the language to compare. Empty field means any.
+
+
- Service
+
- The service to compare. Empty field means any.
+
+
- CA Identification
+
- The CAID to compare. Empty field means any.
+
+
- CA Provider
+
- The CA provider to compare. Empty field means any.
+
+
- PID
+
- Program identification (PID) number to compare. Zero means any.
+ This comparison is processed only when service comparison is active.
+
+
- Action
+
- The rule action defines the operation when all comparisons succeeds.
+
+
+
+ - NONE
+
- No action, may be used for the logging and a comparison verification.
+
+
- USE
+
- Use this elementary stream.
+
+
- ONCE
+
- Use this elementary stream only once per selected language.
+ The first successfully compared rule wins.
+
+
- EXCLUSIVE
+
- Use only this elementary stream. No other elementary streams
+ will be used.
+
+
- EMPTY
+
- Add this elementary stream only when no elementary streams are
+ used from previous rules.
+
+
- IGNORE
+
- Ignore this elementary stream. This stream is not used. Another
+ successfully compared rule with different action may override it.
+
+
+
+ - Log
+
- Write a short message to log identifying the matched parameters.
+ It is useful for debugging your setup or structure of incoming
+ streams.
+
+
+
diff --git a/src/api.c b/src/api.c
index 97546982..83392622 100644
--- a/src/api.c
+++ b/src/api.c
@@ -125,6 +125,7 @@ void api_init ( void )
api_epggrab_init();
api_status_init();
api_imagecache_init();
+ api_esfilter_init();
}
void api_done ( void )
diff --git a/src/api.h b/src/api.h
index 6a275732..f51f7ebe 100644
--- a/src/api.h
+++ b/src/api.h
@@ -67,12 +67,14 @@ void api_epg_init ( void );
void api_epggrab_init ( void );
void api_status_init ( void );
void api_imagecache_init ( void );
+void api_esfilter_init ( void );
/*
* IDnode
*/
typedef struct api_idnode_grid_conf
{
+ int tindex;
int start;
int limit;
idnode_filter_t filter;
diff --git a/src/api/api_esfilter.c b/src/api/api_esfilter.c
new file mode 100644
index 00000000..2769f8c3
--- /dev/null
+++ b/src/api/api_esfilter.c
@@ -0,0 +1,101 @@
+/*
+ * API - elementary stream filter related calls
+ *
+ * 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 "esfilter.h"
+#include "lang_codes.h"
+#include "access.h"
+#include "api.h"
+
+static void
+api_esfilter_grid
+ ( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args,
+ esfilter_class_t cls )
+{
+ esfilter_t *esf;
+
+ TAILQ_FOREACH(esf, &esfilters[cls], esf_link) {
+ idnode_set_add(ins, (idnode_t*)esf, &conf->filter);
+ }
+}
+
+static int
+api_esfilter_create
+ ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp,
+ esfilter_class_t cls )
+{
+ htsmsg_t *conf;
+
+ if (!(conf = htsmsg_get_map(args, "conf")))
+ return EINVAL;
+
+ pthread_mutex_lock(&global_lock);
+ esfilter_create(cls, NULL, conf, 1);
+ pthread_mutex_unlock(&global_lock);
+
+ return 0;
+}
+
+#define ESFILTER(func, t) \
+static void api_esfilter_grid_##func \
+ ( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args ) \
+{ return api_esfilter_grid(ins, conf, args, (t)); } \
+static int api_esfilter_create_##func \
+ ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) \
+{ return api_esfilter_create(opaque, op, args, resp, (t)); }
+
+ESFILTER(video, ESF_CLASS_VIDEO);
+ESFILTER(audio, ESF_CLASS_AUDIO);
+ESFILTER(teletext, ESF_CLASS_TELETEXT);
+ESFILTER(subtit, ESF_CLASS_SUBTIT);
+ESFILTER(ca, ESF_CLASS_CA);
+ESFILTER(other, ESF_CLASS_OTHER);
+
+void api_esfilter_init ( void )
+{
+ static api_hook_t ah[] = {
+ { "esfilter/video/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_video },
+ { "esfilter/video/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_video },
+ { "esfilter/video/create", ACCESS_ADMIN, api_esfilter_create_video, NULL },
+
+ { "esfilter/audio/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_audio },
+ { "esfilter/audio/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_audio },
+ { "esfilter/audio/create", ACCESS_ADMIN, api_esfilter_create_audio, NULL },
+
+ { "esfilter/teletext/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_teletext },
+ { "esfilter/teletext/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_teletext },
+ { "esfilter/teletext/create",ACCESS_ADMIN, api_esfilter_create_teletext, NULL },
+
+ { "esfilter/subtit/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_subtit },
+ { "esfilter/subtit/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_subtit },
+ { "esfilter/subtit/create", ACCESS_ADMIN, api_esfilter_create_subtit, NULL },
+
+ { "esfilter/ca/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_ca },
+ { "esfilter/ca/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_ca },
+ { "esfilter/ca/create", ACCESS_ADMIN, api_esfilter_create_ca, NULL },
+
+ { "esfilter/other/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_other },
+ { "esfilter/other/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_other },
+ { "esfilter/other/create", ACCESS_ADMIN, api_esfilter_create_other, NULL },
+
+ { NULL },
+ };
+
+ api_register_all(ah);
+}
diff --git a/src/api/api_idnode.c b/src/api/api_idnode.c
index 3e951ea1..689a51b5 100644
--- a/src/api/api_idnode.c
+++ b/src/api/api_idnode.c
@@ -377,8 +377,8 @@ exit:
}
static int
-api_idnode_delete
- ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+api_idnode_handler
+ ( htsmsg_t *args, htsmsg_t **resp, void (*handler)(idnode_t *in) )
{
int err = 0;
idnode_t *in;
@@ -400,7 +400,7 @@ api_idnode_delete
HTSMSG_FOREACH(f, uuids) {
if (!(uuid = htsmsg_field_get_string(f))) continue;
if (!(in = idnode_find(uuid, NULL))) continue;
- idnode_delete(in);
+ handler(in);
}
/* Single */
@@ -409,7 +409,7 @@ api_idnode_delete
if (!(in = idnode_find(uuid, NULL)))
err = ENOENT;
else
- idnode_delete(in);
+ handler(in);
}
pthread_mutex_unlock(&global_lock);
@@ -417,14 +417,37 @@ api_idnode_delete
return err;
}
+static int
+api_idnode_delete
+ ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ return api_idnode_handler(args, resp, idnode_delete);
+}
+
+static int
+api_idnode_moveup
+ ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ return api_idnode_handler(args, resp, idnode_moveup);
+}
+
+static int
+api_idnode_movedown
+ ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ return api_idnode_handler(args, resp, idnode_movedown);
+}
+
void api_idnode_init ( void )
{
static api_hook_t ah[] = {
- { "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL },
- { "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL },
- { "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL },
- { "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL },
- { "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL },
+ { "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL },
+ { "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL },
+ { "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL },
+ { "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL },
+ { "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL },
+ { "idnode/moveup", ACCESS_ADMIN, api_idnode_moveup, NULL },
+ { "idnode/movedown", ACCESS_ADMIN, api_idnode_movedown, NULL },
{ NULL },
};
diff --git a/src/api/api_service.c b/src/api/api_service.c
index 211d12ed..e8b847e2 100644
--- a/src/api/api_service.c
+++ b/src/api/api_service.c
@@ -92,12 +92,45 @@ api_service_mapper_notify ( void )
notify_by_msg("servicemapper", api_mapper_status_msg());
}
+static htsmsg_t *
+api_service_streams_get_one ( elementary_stream_t *es )
+{
+ htsmsg_t *e = htsmsg_create_map();
+ htsmsg_add_u32(e, "index", es->es_index);
+ htsmsg_add_u32(e, "pid", es->es_pid);
+ htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type));
+ htsmsg_add_str(e, "language", es->es_lang);
+ if (SCT_ISSUBTITLE(es->es_type)) {
+ htsmsg_add_u32(e, "composition_id", es->es_composition_id);
+ htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id);
+ } else if (SCT_ISAUDIO(es->es_type)) {
+ htsmsg_add_u32(e, "audio_type", es->es_audio_type);
+ } else if (SCT_ISVIDEO(es->es_type)) {
+ htsmsg_add_u32(e, "width", es->es_width);
+ htsmsg_add_u32(e, "height", es->es_height);
+ htsmsg_add_u32(e, "duration", es->es_frame_duration);
+ htsmsg_add_u32(e, "aspect_num", es->es_aspect_num);
+ htsmsg_add_u32(e, "aspect_den", es->es_aspect_den);
+ } else if (es->es_type == SCT_CA) {
+ caid_t *ca;
+ htsmsg_t *e2, *l2 = htsmsg_create_list();
+ LIST_FOREACH(ca, &es->es_caids, link) {
+ e2 = htsmsg_create_map();
+ htsmsg_add_u32(e2, "caid", ca->caid);
+ htsmsg_add_u32(e2, "provider", ca->providerid);
+ htsmsg_add_msg(l2, NULL, e2);
+ }
+ htsmsg_add_msg(e, "caids", l2);
+ }
+ return e;
+}
+
static int
api_service_streams
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
const char *uuid;
- htsmsg_t *e, *st;
+ htsmsg_t *e, *st, *stf;
service_t *s;
elementary_stream_t *es;
@@ -116,6 +149,7 @@ api_service_streams
/* Build response */
pthread_mutex_lock(&s->s_stream_mutex);
st = htsmsg_create_list();
+ stf = htsmsg_create_list();
if (s->s_pcr_pid) {
e = htsmsg_create_map();
htsmsg_add_u32(e, "pid", s->s_pcr_pid);
@@ -128,39 +162,17 @@ api_service_streams
htsmsg_add_str(e, "type", "PMT");
htsmsg_add_msg(st, NULL, e);
}
- TAILQ_FOREACH(es, &s->s_components, es_link) {
- htsmsg_t *e = htsmsg_create_map();
- htsmsg_add_u32(e, "index", es->es_index);
- htsmsg_add_u32(e, "pid", es->es_pid);
- htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type));
- htsmsg_add_str(e, "language", es->es_lang);
- if (SCT_ISSUBTITLE(es->es_type)) {
- htsmsg_add_u32(e, "composition_id", es->es_composition_id);
- htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id);
- } else if (SCT_ISAUDIO(es->es_type)) {
- htsmsg_add_u32(e, "audio_type", es->es_audio_type);
- } else if (SCT_ISVIDEO(es->es_type)) {
- htsmsg_add_u32(e, "width", es->es_width);
- htsmsg_add_u32(e, "height", es->es_height);
- htsmsg_add_u32(e, "duration", es->es_frame_duration);
- htsmsg_add_u32(e, "aspect_num", es->es_aspect_num);
- htsmsg_add_u32(e, "aspect_den", es->es_aspect_den);
- } else if (es->es_type == SCT_CA) {
- caid_t *ca;
- htsmsg_t *e2, *l2 = htsmsg_create_list();
- LIST_FOREACH(ca, &es->es_caids, link) {
- e2 = htsmsg_create_map();
- htsmsg_add_u32(e2, "caid", ca->caid);
- htsmsg_add_u32(e2, "provider", ca->providerid);
- htsmsg_add_msg(l2, NULL, e2);
- }
- htsmsg_add_msg(e, "caids", l2);
- }
- htsmsg_add_msg(st, NULL, e);
- }
+ TAILQ_FOREACH(es, &s->s_components, es_link)
+ htsmsg_add_msg(st, NULL, api_service_streams_get_one(es));
+ if (TAILQ_FIRST(&s->s_filt_components) == NULL ||
+ s->s_status == SERVICE_IDLE)
+ service_build_filter(s);
+ TAILQ_FOREACH(es, &s->s_filt_components, es_filt_link)
+ htsmsg_add_msg(stf, NULL, api_service_streams_get_one(es));
*resp = htsmsg_create_map();
htsmsg_add_str(*resp, "name", s->s_nicename);
htsmsg_add_msg(*resp, "streams", st);
+ htsmsg_add_msg(*resp, "fstreams", stf);
pthread_mutex_unlock(&s->s_stream_mutex);
/* Done */
diff --git a/src/esfilter.c b/src/esfilter.c
new file mode 100644
index 00000000..d5de70a6
--- /dev/null
+++ b/src/esfilter.c
@@ -0,0 +1,1023 @@
+/*
+ * tvheadend, Elementary Stream Filter
+ * 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 "settings.h"
+#include "lang_codes.h"
+#include "service.h"
+#include "esfilter.h"
+
+struct esfilter_entry_queue esfilters[ESF_CLASS_LAST + 1];
+
+static void esfilter_class_save(idnode_t *self);
+
+/*
+ * Class masks
+ */
+uint32_t esfilterclsmask[ESF_CLASS_LAST+1] = {
+ 0,
+ ESF_MASK_VIDEO,
+ ESF_MASK_AUDIO,
+ ESF_MASK_TELETEXT,
+ ESF_MASK_SUBTIT,
+ ESF_MASK_CA,
+ ESF_MASK_OTHER
+};
+
+static const idclass_t *esfilter_classes[ESF_CLASS_LAST+1] = {
+ NULL,
+ &esfilter_class_video,
+ &esfilter_class_audio,
+ &esfilter_class_teletext,
+ &esfilter_class_subtit,
+ &esfilter_class_ca,
+ &esfilter_class_other
+};
+
+/*
+ * Class types
+ */
+
+static struct strtab esfilterclasstab[] = {
+ { "NONE", ESF_CLASS_NONE },
+ { "VIDEO", ESF_CLASS_VIDEO },
+ { "AUDIO", ESF_CLASS_AUDIO },
+ { "TELETEXT", ESF_CLASS_TELETEXT },
+ { "SUBTIT", ESF_CLASS_SUBTIT },
+ { "CA", ESF_CLASS_CA },
+ { "OTHER", ESF_CLASS_OTHER },
+};
+
+const char *
+esfilter_class2txt(int cls)
+{
+ return val2str(cls, esfilterclasstab) ?: "INVALID";
+}
+
+#if 0
+static int
+esfilter_txt2class(const char *s)
+{
+ return s ? str2val(s, esfilterclasstab) : ESF_CLASS_NONE;
+}
+#endif
+
+/*
+ * Action types
+ */
+
+static struct strtab esfilteractiontab[] = {
+ { "NONE", ESFA_NONE },
+ { "USE", ESFA_USE },
+ { "ONCE", ESFA_ONCE },
+ { "EXCLUSIVE", ESFA_EXCLUSIVE },
+ { "EMPTY", ESFA_EMPTY },
+ { "IGNORE", ESFA_IGNORE }
+};
+
+const char *
+esfilter_action2txt(esfilter_action_t a)
+{
+ return val2str(a, esfilteractiontab) ?: "INVALID";
+}
+
+#if 0
+static esfilter_action_t
+esfilter_txt2action(const char *s)
+{
+ return s ? str2val(s, esfilteractiontab) : ESFA_NONE;
+}
+#endif
+
+/*
+ * Create / delete
+ */
+
+static void
+esfilter_reindex(esfilter_class_t cls)
+{
+ esfilter_t *esf;
+ int i = 1;
+
+ TAILQ_FOREACH(esf, &esfilters[cls], esf_link)
+ esf->esf_save = 0;
+ TAILQ_FOREACH(esf, &esfilters[cls], esf_link) {
+ if (esf->esf_index != i) {
+ esf->esf_index = i;
+ esf->esf_save = 1;
+ }
+ i++;
+ }
+ TAILQ_FOREACH(esf, &esfilters[cls], esf_link)
+ if (esf->esf_save) {
+ esf->esf_save = 0;
+ esfilter_class_save((idnode_t *)esf);
+ }
+}
+
+static int
+esfilter_cmp(esfilter_t *a, esfilter_t *b)
+{
+ return a->esf_index - b->esf_index;
+}
+
+esfilter_t *
+esfilter_create
+ (esfilter_class_t cls, const char *uuid, htsmsg_t *conf, int save)
+{
+ esfilter_t *esf = calloc(1, sizeof(*esf));
+ const idclass_t *c = NULL;
+ uint32_t ct;
+
+ esf->esf_caid = -1;
+ esf->esf_caprovider = -1;
+ if (ESF_CLASS_IS_VALID(cls)) {
+ c = esfilter_classes[cls];
+ } else {
+ if (!htsmsg_get_u32(conf, "class", &ct)) {
+ cls = ct;
+ if (ESF_CLASS_IS_VALID(cls))
+ c = esfilter_classes[cls];
+ }
+ }
+ if (!c) {
+ tvherror("esfilter", "wrong class %d!", cls);
+ abort();
+ }
+ lock_assert(&global_lock);
+ idnode_insert(&esf->esf_id, uuid, c);
+ if (conf)
+ idnode_load(&esf->esf_id, conf);
+ if (ESF_CLASS_IS_VALID(cls))
+ esf->esf_class = cls;
+ else if (!ESF_CLASS_IS_VALID(esf->esf_class)) {
+ tvherror("esfilter", "wrong class %d!", esf->esf_class);
+ abort();
+ }
+ if (esf->esf_index) {
+ TAILQ_INSERT_SORTED(&esfilters[esf->esf_class], esf, esf_link, esfilter_cmp);
+ } else {
+ TAILQ_INSERT_TAIL(&esfilters[esf->esf_class], esf, esf_link);
+ esfilter_reindex(esf->esf_class);
+ }
+ if (save)
+ esfilter_class_save((idnode_t *)esf);
+ return esf;
+}
+
+static void
+esfilter_delete(esfilter_t *esf, int delconf)
+{
+ if (delconf)
+ hts_settings_remove("esfilter/%s", idnode_uuid_as_str(&esf->esf_id));
+ TAILQ_REMOVE(&esfilters[esf->esf_class], esf, esf_link);
+ idnode_unlink(&esf->esf_id);
+ free(esf->esf_comment);
+ free(esf);
+}
+
+/*
+ * Class functions
+ */
+
+static void
+esfilter_class_save(idnode_t *self)
+{
+ htsmsg_t *c = htsmsg_create_map();
+ idnode_save(self, c);
+ hts_settings_save(c, "esfilter/%s", idnode_uuid_as_str(self));
+ htsmsg_destroy(c);
+}
+
+static const char *
+esfilter_class_get_title(idnode_t *self)
+{
+ esfilter_t *esf = (esfilter_t *)self;
+ return idnode_uuid_as_str(&esf->esf_id);
+}
+
+static void
+esfilter_class_delete(idnode_t *self)
+{
+ esfilter_t *esf = (esfilter_t *)self;
+ esfilter_delete(esf, 1);
+}
+
+static void
+esfilter_class_moveup(idnode_t *self)
+{
+ esfilter_t *esf = (esfilter_t *)self;
+ esfilter_t *prev = TAILQ_PREV(esf, esfilter_entry_queue, esf_link);
+ if (prev) {
+ TAILQ_REMOVE(&esfilters[esf->esf_class], esf, esf_link);
+ TAILQ_INSERT_BEFORE(prev, esf, esf_link);
+ esfilter_reindex(esf->esf_class);
+ }
+}
+
+static void
+esfilter_class_movedown(idnode_t *self)
+{
+ esfilter_t *esf = (esfilter_t *)self;
+ esfilter_t *next = TAILQ_NEXT(esf, esf_link);
+ if (next) {
+ TAILQ_REMOVE(&esfilters[esf->esf_class], esf, esf_link);
+ TAILQ_INSERT_AFTER(&esfilters[esf->esf_class], next, esf, esf_link);
+ esfilter_reindex(esf->esf_class);
+ }
+}
+
+static const void *
+esfilter_class_type_get(void *o)
+{
+ esfilter_t *esf = o;
+ htsmsg_t *l = htsmsg_create_list();
+ int i;
+
+ for (i = SCT_UNKNOWN; i <= SCT_LAST; i++)
+ if ((esf->esf_type & SCT_MASK(i)) != 0)
+ htsmsg_add_u32(l, NULL, i);
+ return l;
+}
+
+static char *
+esfilter_class_type_rend (void *o)
+{
+ char *str;
+ htsmsg_t *l = htsmsg_create_list();
+ esfilter_t *esf = o;
+ int i;
+
+ for (i = SCT_UNKNOWN; i <= SCT_LAST; i++) {
+ if (SCT_MASK(i) & esf->esf_type)
+ htsmsg_add_str(l, NULL, streaming_component_type2txt(i));
+ }
+
+ str = htsmsg_list_2_csv(l);
+ htsmsg_destroy(l);
+ return str;
+}
+
+static int
+esfilter_class_type_set_(void *o, const void *v, esfilter_class_t cls)
+{
+ esfilter_t *esf = o;
+ htsmsg_t *types = (htsmsg_t*)v;
+ htsmsg_field_t *f;
+ uint32_t mask = 0, u32;
+ uint32_t vmask = esfilterclsmask[cls];
+ int save;
+
+ HTSMSG_FOREACH(f, types) {
+ if (!htsmsg_field_get_u32(f, &u32)) {
+ if (SCT_MASK(u32) & vmask)
+ mask |= SCT_MASK(u32);
+ } else {
+ return 0;
+ }
+ }
+ save = esf->esf_type != mask;
+ esf->esf_type = mask;
+ return save;
+}
+
+static htsmsg_t *
+esfilter_class_type_enum_(void *o, esfilter_class_t cls)
+{
+ uint32_t mask = esfilterclsmask[cls];
+ htsmsg_t *l = htsmsg_create_list();
+ int i;
+
+ for (i = SCT_UNKNOWN; i <= SCT_LAST; i++) {
+ if (mask & SCT_MASK(i)) {
+ htsmsg_t *e = htsmsg_create_map();
+ htsmsg_add_u32(e, "key", i);
+ htsmsg_add_str(e, "val",
+ i == SCT_UNKNOWN ? "ANY" : streaming_component_type2txt(i));
+ htsmsg_add_msg(l, NULL, e);
+ }
+ }
+ return l;
+}
+
+#define ESFILTER_CLS(func, type) \
+static int esfilter_class_type_set_##func(void *o, const void *v) \
+ { return esfilter_class_type_set_(o, v, type); } \
+static htsmsg_t * esfilter_class_type_enum_##func(void *o) \
+ { return esfilter_class_type_enum_(o, type); }
+
+ESFILTER_CLS(video, ESF_CLASS_VIDEO);
+ESFILTER_CLS(audio, ESF_CLASS_AUDIO);
+ESFILTER_CLS(teletext, ESF_CLASS_TELETEXT);
+ESFILTER_CLS(subtit, ESF_CLASS_SUBTIT);
+ESFILTER_CLS(ca, ESF_CLASS_CA);
+ESFILTER_CLS(other, ESF_CLASS_OTHER);
+
+static const void *
+esfilter_class_language_get(void *o)
+{
+ static __thread char *ret;
+ esfilter_t *esf = o;
+ ret = esf->esf_language;
+ return &ret;
+}
+
+static int
+esfilter_class_language_set(void *o, const void *v)
+{
+ esfilter_t *esf = o;
+ const char *s = v;
+ char n[4];
+ int save;
+ strncpy(n, s && s[0] ? lang_code_get(s) : "", 4);
+ n[3] = 0;
+ save = strcmp(esf->esf_language, n);
+ strcpy(esf->esf_language, n);
+ return save;
+}
+
+static htsmsg_t *
+esfilter_class_language_enum(void *o)
+{
+ htsmsg_t *l = htsmsg_create_list();
+ const lang_code_t *lc = lang_codes;
+ char buf[128];
+
+ while (lc->code2b) {
+ htsmsg_t *e = htsmsg_create_map();
+ if (!strcmp(lc->code2b, "und")) {
+ htsmsg_add_str(e, "key", "");
+ htsmsg_add_str(e, "val", "ANY");
+ } else {
+ htsmsg_add_str(e, "key", lc->code2b);
+ snprintf(buf, sizeof(buf), "%s (%s)", lc->desc, lc->code2b);
+ buf[sizeof(buf)-1] = '\0';
+ htsmsg_add_str(e, "val", buf);
+ }
+ htsmsg_add_msg(l, NULL, e);
+ lc++;
+ }
+ return l;
+}
+
+static const void *
+esfilter_class_service_get(void *o)
+{
+ static __thread char *ret;
+ esfilter_t *esf = o;
+ ret = esf->esf_service;
+ return &ret;
+}
+
+static int
+esfilter_class_service_set(void *o, const void *v)
+{
+ esfilter_t *esf = o;
+ const char *s = v;
+ int save = 0;
+ if (strncmp(esf->esf_service, s, UUID_HEX_SIZE)) {
+ strncpy(esf->esf_service, s, UUID_HEX_SIZE);
+ esf->esf_service[UUID_HEX_SIZE-1] = '\0';
+ save = 1;
+ }
+ return save;
+}
+
+static htsmsg_t *
+esfilter_class_service_enum(void *o)
+{
+ htsmsg_t *e, *m = htsmsg_create_map();
+ htsmsg_add_str(m, "type", "api");
+ htsmsg_add_str(m, "uri", "service/list");
+ htsmsg_add_str(m, "event", "service");
+ e = htsmsg_create_map();
+ htsmsg_add_bool(e, "enum", 1);
+ htsmsg_add_msg(m, "params", e);
+ return m;
+}
+
+#define MAX_ITEMS 256
+
+static int
+esfilter_build_ca_cmp(const void *_a, const void *_b)
+{
+ uint32_t a = *(uint32_t *)_a;
+ uint32_t b = *(uint32_t *)_b;
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ return 0;
+}
+
+static htsmsg_t *
+esfilter_build_ca_enum(int provider)
+{
+ htsmsg_t *e, *l;
+ uint32_t *a = alloca(sizeof(uint32_t) * MAX_ITEMS);
+ char buf[16], buf2[128];
+ service_t *s;
+ elementary_stream_t *es;
+ caid_t *ca;
+ uint32_t v;
+ int i, count = 0;
+
+ lock_assert(&global_lock);
+ TAILQ_FOREACH(s, &service_all, s_all_link) {
+ pthread_mutex_lock(&s->s_stream_mutex);
+ TAILQ_FOREACH(es, &s->s_components, es_link) {
+ LIST_FOREACH(ca, &es->es_caids, link) {
+ v = provider ? ca->providerid : ca->caid;
+ for (i = 0; i < count; i++)
+ if (a[i] == v)
+ break;
+ if (i >= count)
+ a[count++] = v;
+ }
+ }
+ pthread_mutex_unlock(&s->s_stream_mutex);
+ }
+ qsort(a, count, sizeof(uint32_t), esfilter_build_ca_cmp);
+
+ l = htsmsg_create_list();
+
+ e = htsmsg_create_map();
+ htsmsg_add_str(e, "key", provider ? "ffffff" : "ffff");
+ htsmsg_add_str(e, "val", "ANY");
+ htsmsg_add_msg(l, NULL, e);
+
+ for (i = 0; i < count; i++) {
+ e = htsmsg_create_map();
+ snprintf(buf, sizeof(buf), provider ? "%06x" : "%04x", a[i]);
+ if (!provider)
+ snprintf(buf2, sizeof(buf2), provider ? "%06x %s" : "%04x - %s",
+ a[i], descrambler_caid2name(a[i]));
+ htsmsg_add_str(e, "key", buf);
+ htsmsg_add_str(e, "val", provider ? buf : buf2);
+ htsmsg_add_msg(l, NULL, e);
+ }
+ return l;
+
+}
+
+static const void *
+esfilter_class_caid_get(void *o)
+{
+ static __thread char *ret;
+ static __thread char buf[16];
+ esfilter_t *esf = o;
+ snprintf(buf, sizeof(buf), "%04x", esf->esf_caid);
+ ret = buf;
+ return &ret;
+}
+
+static int
+esfilter_class_caid_set(void *o, const void *v)
+{
+ esfilter_t *esf = o;
+ uint16_t u;
+ int save = 0;
+ u = strtol(v, NULL, 16);
+ if (u != esf->esf_caid) {
+ esf->esf_caid = u;
+ save = 1;
+ }
+ return save;
+}
+
+static htsmsg_t *
+esfilter_class_caid_enum(void *o)
+{
+ return esfilter_build_ca_enum(0);
+}
+
+static const void *
+esfilter_class_caprovider_get(void *o)
+{
+ static __thread char *ret;
+ static __thread char buf[16];
+ esfilter_t *esf = o;
+ if (esf->esf_caprovider == -1)
+ strcpy(buf, "ffffff");
+ else
+ snprintf(buf, sizeof(buf), "%06x", esf->esf_caprovider);
+ ret = buf;
+ return &ret;
+}
+
+static int
+esfilter_class_caprovider_set(void *o, const void *v)
+{
+ esfilter_t *esf = o;
+ uint32_t u;
+ int save = 0;
+ if (strcmp(v, "ffffff") == 0)
+ u = -1;
+ else
+ u = strtol(v, NULL, 16);
+ if (u != esf->esf_caprovider) {
+ esf->esf_caprovider = u;
+ save = 1;
+ }
+ return save;
+}
+
+static htsmsg_t *
+esfilter_class_caprovider_enum(void *o)
+{
+ return esfilter_build_ca_enum(1);
+}
+
+static const void *
+esfilter_class_action_get(void *o)
+{
+ esfilter_t *esf = o;
+ return &esf->esf_action;
+}
+
+static int
+esfilter_class_action_set(void *o, const void *v)
+{
+ esfilter_t *esf = o;
+ int n = *(int *)v;
+ int save = 0;
+ if (n >= ESFA_USE && n <= ESFA_LAST) {
+ save = esf->esf_action != n;
+ esf->esf_action = n;
+ }
+ return save;
+}
+
+static htsmsg_t *
+esfilter_class_action_enum(void *o)
+{
+ htsmsg_t *l = htsmsg_create_list();
+ int i;
+
+ for (i = ESFA_NONE; i <= ESFA_LAST; i++) {
+ htsmsg_t *e = htsmsg_create_map();
+ htsmsg_add_u32(e, "key", i);
+ htsmsg_add_str(e, "val", esfilter_action2txt(i));
+ htsmsg_add_msg(l, NULL, e);
+ }
+ return l;
+}
+
+const idclass_t esfilter_class = {
+ .ic_class = "esfilter",
+ .ic_caption = "Elementary Stream Filter",
+ .ic_save = esfilter_class_save,
+ .ic_get_title = esfilter_class_get_title,
+ .ic_delete = esfilter_class_delete,
+ .ic_moveup = esfilter_class_moveup,
+ .ic_movedown = esfilter_class_movedown,
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_INT,
+ .id = "class",
+ .name = "Class",
+ .opts = PO_RDONLY | PO_HIDDEN,
+ .off = offsetof(esfilter_t, esf_class),
+ },
+ {
+ .type = PT_INT,
+ .id = "index",
+ .name = "Index",
+ .opts = PO_RDONLY | PO_HIDDEN,
+ .off = offsetof(esfilter_t, esf_index),
+ },
+ {
+ .type = PT_BOOL,
+ .id = "enabled",
+ .name = "Enabled",
+ .off = offsetof(esfilter_t, esf_enabled),
+ },
+ {}
+ }
+};
+
+const idclass_t esfilter_class_video = {
+ .ic_super = &esfilter_class,
+ .ic_class = "esfilter_video",
+ .ic_caption = "Video Stream Filter",
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .islist = 1,
+ .id = "type",
+ .name = "Stream Type",
+ .get = esfilter_class_type_get,
+ .set = esfilter_class_type_set_video,
+ .list = esfilter_class_type_enum_video,
+ .rend = esfilter_class_type_rend,
+ },
+ {
+ .type = PT_STR,
+ .id = "language",
+ .name = "Language",
+ .get = esfilter_class_language_get,
+ .set = esfilter_class_language_set,
+ .list = esfilter_class_language_enum,
+ },
+ {
+ .type = PT_STR,
+ .id = "service",
+ .name = "Service",
+ .get = esfilter_class_service_get,
+ .set = esfilter_class_service_set,
+ .list = esfilter_class_service_enum,
+ },
+ {
+ .type = PT_INT,
+ .id = "pid",
+ .name = "PID",
+ .off = offsetof(esfilter_t, esf_pid),
+ },
+ {
+ .type = PT_INT,
+ .id = "action",
+ .name = "Action",
+ .get = esfilter_class_action_get,
+ .set = esfilter_class_action_set,
+ .list = esfilter_class_action_enum,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "log",
+ .name = "Log",
+ .off = offsetof(esfilter_t, esf_log),
+ },
+ {
+ .type = PT_STR,
+ .id = "comment",
+ .name = "Comment",
+ .off = offsetof(esfilter_t, esf_comment),
+ },
+ {}
+ }
+};
+
+const idclass_t esfilter_class_audio = {
+ .ic_super = &esfilter_class,
+ .ic_class = "esfilter_audio",
+ .ic_caption = "Audio Stream Filter",
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .islist = 1,
+ .id = "type",
+ .name = "Stream Type",
+ .get = esfilter_class_type_get,
+ .set = esfilter_class_type_set_audio,
+ .list = esfilter_class_type_enum_audio,
+ .rend = esfilter_class_type_rend,
+ },
+ {
+ .type = PT_STR,
+ .id = "language",
+ .name = "Language",
+ .get = esfilter_class_language_get,
+ .set = esfilter_class_language_set,
+ .list = esfilter_class_language_enum,
+ },
+ {
+ .type = PT_STR,
+ .id = "service",
+ .name = "Service",
+ .get = esfilter_class_service_get,
+ .set = esfilter_class_service_set,
+ .list = esfilter_class_service_enum,
+ },
+ {
+ .type = PT_INT,
+ .id = "pid",
+ .name = "PID",
+ .off = offsetof(esfilter_t, esf_pid),
+ },
+ {
+ .type = PT_INT,
+ .id = "action",
+ .name = "Action",
+ .get = esfilter_class_action_get,
+ .set = esfilter_class_action_set,
+ .list = esfilter_class_action_enum,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "log",
+ .name = "Log",
+ .off = offsetof(esfilter_t, esf_log),
+ },
+ {
+ .type = PT_STR,
+ .id = "comment",
+ .name = "Comment",
+ .off = offsetof(esfilter_t, esf_comment),
+ },
+ {}
+ }
+};
+
+const idclass_t esfilter_class_teletext = {
+ .ic_super = &esfilter_class,
+ .ic_class = "esfilter_teletext",
+ .ic_caption = "Teletext Stream Filter",
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .islist = 1,
+ .id = "type",
+ .name = "Stream Type",
+ .get = esfilter_class_type_get,
+ .set = esfilter_class_type_set_teletext,
+ .list = esfilter_class_type_enum_teletext,
+ .rend = esfilter_class_type_rend,
+ },
+ {
+ .type = PT_STR,
+ .id = "language",
+ .name = "Language",
+ .get = esfilter_class_language_get,
+ .set = esfilter_class_language_set,
+ .list = esfilter_class_language_enum,
+ },
+ {
+ .type = PT_STR,
+ .id = "service",
+ .name = "Service",
+ .get = esfilter_class_service_get,
+ .set = esfilter_class_service_set,
+ .list = esfilter_class_service_enum,
+ },
+ {
+ .type = PT_INT,
+ .id = "pid",
+ .name = "PID",
+ .off = offsetof(esfilter_t, esf_pid),
+ },
+ {
+ .type = PT_INT,
+ .id = "action",
+ .name = "Action",
+ .get = esfilter_class_action_get,
+ .set = esfilter_class_action_set,
+ .list = esfilter_class_action_enum,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "log",
+ .name = "Log",
+ .off = offsetof(esfilter_t, esf_log),
+ },
+ {
+ .type = PT_STR,
+ .id = "comment",
+ .name = "Comment",
+ .off = offsetof(esfilter_t, esf_comment),
+ },
+ {}
+ }
+};
+
+const idclass_t esfilter_class_subtit = {
+ .ic_super = &esfilter_class,
+ .ic_class = "esfilter_subtit",
+ .ic_caption = "Subtitle Stream Filter",
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .islist = 1,
+ .id = "type",
+ .name = "Stream Type",
+ .get = esfilter_class_type_get,
+ .set = esfilter_class_type_set_subtit,
+ .list = esfilter_class_type_enum_subtit,
+ .rend = esfilter_class_type_rend,
+ },
+ {
+ .type = PT_STR,
+ .id = "language",
+ .name = "Language",
+ .get = esfilter_class_language_get,
+ .set = esfilter_class_language_set,
+ .list = esfilter_class_language_enum,
+ },
+ {
+ .type = PT_STR,
+ .id = "service",
+ .name = "Service",
+ .get = esfilter_class_service_get,
+ .set = esfilter_class_service_set,
+ .list = esfilter_class_service_enum,
+ },
+ {
+ .type = PT_INT,
+ .id = "pid",
+ .name = "PID",
+ .off = offsetof(esfilter_t, esf_pid),
+ },
+ {
+ .type = PT_INT,
+ .id = "action",
+ .name = "Action",
+ .get = esfilter_class_action_get,
+ .set = esfilter_class_action_set,
+ .list = esfilter_class_action_enum,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "log",
+ .name = "Log",
+ .off = offsetof(esfilter_t, esf_log),
+ },
+ {
+ .type = PT_STR,
+ .id = "comment",
+ .name = "Comment",
+ .off = offsetof(esfilter_t, esf_comment),
+ },
+ {}
+ }
+};
+
+const idclass_t esfilter_class_ca = {
+ .ic_super = &esfilter_class,
+ .ic_class = "esfilter_ca",
+ .ic_caption = "CA Stream Filter",
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .islist = 1,
+ .id = "type",
+ .name = "Stream Type",
+ .get = esfilter_class_type_get,
+ .set = esfilter_class_type_set_ca,
+ .list = esfilter_class_type_enum_ca,
+ .rend = esfilter_class_type_rend,
+ },
+ {
+ .type = PT_STR,
+ .id = "CAid",
+ .name = "CA Identification",
+ .get = esfilter_class_caid_get,
+ .set = esfilter_class_caid_set,
+ .list = esfilter_class_caid_enum,
+ },
+ {
+ .type = PT_STR,
+ .id = "CAprovider",
+ .name = "CA Provider",
+ .get = esfilter_class_caprovider_get,
+ .set = esfilter_class_caprovider_set,
+ .list = esfilter_class_caprovider_enum,
+ },
+ {
+ .type = PT_STR,
+ .id = "service",
+ .name = "Service",
+ .get = esfilter_class_service_get,
+ .set = esfilter_class_service_set,
+ .list = esfilter_class_service_enum,
+ },
+ {
+ .type = PT_INT,
+ .id = "pid",
+ .name = "PID",
+ .off = offsetof(esfilter_t, esf_pid),
+ },
+ {
+ .type = PT_INT,
+ .id = "action",
+ .name = "Action",
+ .get = esfilter_class_action_get,
+ .set = esfilter_class_action_set,
+ .list = esfilter_class_action_enum,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "log",
+ .name = "Log",
+ .off = offsetof(esfilter_t, esf_log),
+ },
+ {
+ .type = PT_STR,
+ .id = "comment",
+ .name = "Comment",
+ .off = offsetof(esfilter_t, esf_comment),
+ },
+ {}
+ }
+};
+
+const idclass_t esfilter_class_other = {
+ .ic_super = &esfilter_class,
+ .ic_class = "esfilter_other",
+ .ic_caption = "Other Stream Filter",
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .islist = 1,
+ .id = "type",
+ .name = "Stream Type",
+ .get = esfilter_class_type_get,
+ .set = esfilter_class_type_set_other,
+ .list = esfilter_class_type_enum_other,
+ .rend = esfilter_class_type_rend,
+ },
+ {
+ .type = PT_STR,
+ .id = "language",
+ .name = "Language",
+ .get = esfilter_class_language_get,
+ .set = esfilter_class_language_set,
+ .list = esfilter_class_language_enum,
+ },
+ {
+ .type = PT_STR,
+ .id = "service",
+ .name = "Service",
+ .get = esfilter_class_service_get,
+ .set = esfilter_class_service_set,
+ .list = esfilter_class_service_enum,
+ },
+ {
+ .type = PT_INT,
+ .id = "pid",
+ .name = "PID",
+ .off = offsetof(esfilter_t, esf_pid),
+ },
+ {
+ .type = PT_INT,
+ .id = "action",
+ .name = "Action",
+ .get = esfilter_class_action_get,
+ .set = esfilter_class_action_set,
+ .list = esfilter_class_action_enum,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "log",
+ .name = "Log",
+ .off = offsetof(esfilter_t, esf_log),
+ },
+ {
+ .type = PT_STR,
+ .id = "comment",
+ .name = "Comment",
+ .off = offsetof(esfilter_t, esf_comment),
+ },
+ {}
+ }
+};
+
+/**
+ * Initialize
+ */
+void
+esfilter_init(void)
+{
+ htsmsg_t *c, *e;
+ htsmsg_field_t *f;
+ int i;
+
+ for (i = 0; i <= ESF_CLASS_LAST; i++)
+ TAILQ_INIT(&esfilters[i]);
+
+ if (!(c = hts_settings_load_r(1, "esfilter")))
+ return;
+ HTSMSG_FOREACH(f, c) {
+ if (!(e = htsmsg_field_get_map(f)))
+ continue;
+ esfilter_create(-1, f->hmf_name, e, 0);
+ }
+ htsmsg_destroy(c);
+}
+
+void
+esfilter_done(void)
+{
+ esfilter_t *esf;
+ int i;
+
+ pthread_mutex_lock(&global_lock);
+ for (i = 0; i <= ESF_CLASS_LAST; i++) {
+ while ((esf = TAILQ_FIRST(&esfilters[i])) != NULL)
+ esfilter_delete(esf, 0);
+ }
+ pthread_mutex_unlock(&global_lock);
+}
diff --git a/src/esfilter.h b/src/esfilter.h
new file mode 100644
index 00000000..e65ae9c6
--- /dev/null
+++ b/src/esfilter.h
@@ -0,0 +1,110 @@
+/*
+ * tvheadend, Elementary Stream Filter
+ * 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_ESFILTER_H__
+#define __TVH_ESFILTER_H__
+
+#include "tvheadend.h"
+#include "idnode.h"
+
+typedef enum {
+ ESF_CLASS_NONE = 0,
+ ESF_CLASS_VIDEO,
+ ESF_CLASS_AUDIO,
+ ESF_CLASS_TELETEXT,
+ ESF_CLASS_SUBTIT,
+ ESF_CLASS_CA,
+ ESF_CLASS_OTHER,
+ ESF_CLASS_LAST = ESF_CLASS_OTHER
+} esfilter_class_t;
+
+#define ESF_CLASS_IS_VALID(i) \
+ ((i) >= ESF_CLASS_VIDEO && (i) <= ESF_CLASS_LAST)
+
+extern const idclass_t esfilter_class;
+extern const idclass_t esfilter_class_video;
+extern const idclass_t esfilter_class_audio;
+extern const idclass_t esfilter_class_teletext;
+extern const idclass_t esfilter_class_subtit;
+extern const idclass_t esfilter_class_ca;
+extern const idclass_t esfilter_class_other;
+
+#define ESF_MASK_VIDEO \
+ (SCT_MASK(SCT_MPEG2VIDEO) | SCT_MASK(SCT_H264) | SCT_MASK(SCT_VP8))
+
+#define ESF_MASK_AUDIO \
+ (SCT_MASK(SCT_MPEG2AUDIO) | SCT_MASK(SCT_AC3) | SCT_MASK(SCT_AAC) | \
+ SCT_MASK(SCT_EAC3) | SCT_MASK(SCT_MP4A) | SCT_MASK(SCT_VORBIS))
+
+#define ESF_MASK_TELETEXT \
+ SCT_MASK(SCT_TELETEXT)
+
+#define ESF_MASK_SUBTIT \
+ (SCT_MASK(SCT_DVBSUB) | SCT_MASK(SCT_TEXTSUB))
+
+#define ESF_MASK_CA \
+ SCT_MASK(SCT_CA)
+
+#define ESF_MASK_OTHER \
+ SCT_MASK(SCT_MPEGTS)
+
+extern uint32_t esfilterclsmask[];
+
+TAILQ_HEAD(esfilter_entry_queue, esfilter);
+
+extern struct esfilter_entry_queue esfilters[];
+
+typedef enum {
+ ESFA_NONE = 0,
+ ESFA_USE, /* use this stream */
+ ESFA_ONCE, /* use this stream once per language */
+ ESFA_EXCLUSIVE, /* use this stream exclusively */
+ ESFA_EMPTY, /* use this stream when no streams were added */
+ ESFA_IGNORE,
+ ESFA_LAST = ESFA_IGNORE
+} esfilter_action_t;
+
+typedef struct esfilter {
+ idnode_t esf_id;
+ TAILQ_ENTRY(esfilter) esf_link;
+
+ int esf_class;
+ int esf_save;
+ int esf_index;
+ int esf_enabled;
+ uint32_t esf_type;
+ char esf_language[4];
+ char esf_service[UUID_HEX_SIZE];
+ int esf_pid;
+ uint16_t esf_caid;
+ uint32_t esf_caprovider;
+ int esf_action;
+ int esf_log;
+ char *esf_comment;
+} esfilter_t;
+
+esfilter_t *esfilter_create
+ (esfilter_class_t esf_class, const char *uuid, htsmsg_t *conf, int save);
+
+const char * esfilter_class2txt(int cls);
+const char * esfilter_action2txt(esfilter_action_t a);
+
+void esfilter_init(void);
+void esfilter_done(void);
+
+#endif /* __TVH_ESFILTER_H__ */
diff --git a/src/idnode.c b/src/idnode.c
index 13ba418c..192e929a 100644
--- a/src/idnode.c
+++ b/src/idnode.c
@@ -140,20 +140,40 @@ idnode_unlink(idnode_t *in)
/**
*
*/
-void
-idnode_delete(idnode_t *in)
+static void
+idnode_handler(size_t off, idnode_t *in)
{
+ void (**fcn)(idnode_t *);
lock_assert(&global_lock);
const idclass_t *idc = in->in_class;
while (idc) {
- if (idc->ic_delete) {
- idc->ic_delete(in);
+ fcn = (void *)idc + off;
+ if (*fcn) {
+ (*fcn)(in);
break;
}
idc = idc->ic_super;
}
}
+void
+idnode_delete(idnode_t *in)
+{
+ return idnode_handler(offsetof(idclass_t, ic_delete), in);
+}
+
+void
+idnode_moveup(idnode_t *in)
+{
+ return idnode_handler(offsetof(idclass_t, ic_moveup), in);
+}
+
+void
+idnode_movedown(idnode_t *in)
+{
+ return idnode_handler(offsetof(idclass_t, ic_movedown), in);
+}
+
/* **************************************************************************
* Info
* *************************************************************************/
diff --git a/src/idnode.h b/src/idnode.h
index 4bab5b9c..e5aaf7db 100644
--- a/src/idnode.h
+++ b/src/idnode.h
@@ -42,7 +42,8 @@ typedef struct idnode_set
/*
* Class definition
*/
-typedef struct idclass {
+typedef struct idclass idclass_t;
+struct idclass {
const struct idclass *ic_super; /// Parent class
const char *ic_class; /// Class name
const char *ic_caption; /// Class description
@@ -51,11 +52,13 @@ typedef struct idclass {
const char *ic_event; /// Events to fire on add/delete/title
/* Callbacks */
- idnode_set_t *(*ic_get_childs)(idnode_t *self);
- const char *(*ic_get_title) (idnode_t *self);
- void (*ic_save) (idnode_t *self);
- void (*ic_delete) (idnode_t *self);
-} idclass_t;
+ idnode_set_t *(*ic_get_childs) (idnode_t *self);
+ const char *(*ic_get_title) (idnode_t *self);
+ void (*ic_save) (idnode_t *self);
+ void (*ic_delete) (idnode_t *self);
+ void (*ic_moveup) (idnode_t *self);
+ void (*ic_movedown) (idnode_t *self);
+};
/*
* Node definition
@@ -120,6 +123,8 @@ const char *idnode_get_title (idnode_t *in);
int idnode_is_leaf (idnode_t *in);
int idnode_is_instance (idnode_t *in, const idclass_t *idc);
void idnode_delete (idnode_t *in);
+void idnode_moveup (idnode_t *in);
+void idnode_movedown (idnode_t *in);
void *idnode_find (const char *uuid, const idclass_t *idc);
idnode_set_t *idnode_find_all(const idclass_t *idc);
diff --git a/src/main.c b/src/main.c
index 8d4add02..3dcd09c9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -61,6 +61,7 @@
#include "timeshift.h"
#include "fsmonitor.h"
#include "lang_codes.h"
+#include "esfilter.h"
#if ENABLE_LIBAV
#include "libav.h"
#include "plumbing/transcoding.h"
@@ -767,6 +768,7 @@ main(int argc, char **argv)
imagecache_init();
http_client_init();
+ esfilter_init();
service_init();
@@ -877,6 +879,7 @@ main(int argc, char **argv)
tvhftrace("main", hts_settings_done);
tvhftrace("main", dvb_done);
tvhftrace("main", lang_str_done);
+ tvhftrace("main", esfilter_done);
tvhftrace("main", urlparse_done);
tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend");
diff --git a/src/service.c b/src/service.c
index fd765e79..7c34e652 100644
--- a/src/service.c
+++ b/src/service.c
@@ -44,6 +44,7 @@
#include "lang_codes.h"
#include "descrambler.h"
#include "input.h"
+#include "esfilter.h"
static void service_data_timeout(void *aux);
static void service_class_save(struct idnode *self);
@@ -284,7 +285,7 @@ service_stop(service_t *t)
/**
* Clean up each stream
*/
- TAILQ_FOREACH(st, &t->s_components, es_link)
+ TAILQ_FOREACH(st, &t->s_filt_components, es_link)
stream_clean(st);
t->s_status = SERVICE_IDLE;
@@ -319,6 +320,161 @@ service_remove_subscriber(service_t *t, th_subscription_t *s,
}
+/**
+ *
+ */
+#define ESFM_USED (1<<0)
+#define ESFM_IGNORE (1<<1)
+
+static void
+service_build_filter_add(service_t *t, elementary_stream_t *st,
+ elementary_stream_t **sta, int *p)
+{
+ /* only once */
+ if (st->es_filter & ESFM_USED)
+ return;
+ st->es_filter |= ESFM_USED;
+ TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link);
+ sta[*p] = st;
+ (*p)++;
+}
+
+/**
+ *
+ */
+void
+service_build_filter(service_t *t)
+{
+ elementary_stream_t *st, *st2, **sta;
+ esfilter_t *esf;
+ caid_t *ca;
+ int i, n, p, o, exclusive;
+ uint32_t mask;
+
+ /* rebuild the filtered and ordered components */
+ TAILQ_INIT(&t->s_filt_components);
+
+ for (i = ESF_CLASS_VIDEO; i <= ESF_CLASS_LAST; i++)
+ if (!TAILQ_EMPTY(&esfilters[i]))
+ goto filter;
+
+ TAILQ_FOREACH(st, &t->s_components, es_link)
+ TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link);
+ return;
+
+filter:
+ n = 0;
+ TAILQ_FOREACH(st, &t->s_components, es_link) {
+ st->es_filter = 0;
+ n++;
+ }
+
+ sta = alloca(sizeof(elementary_stream_t *) * n);
+
+ for (i = ESF_CLASS_VIDEO, p = 0; i <= ESF_CLASS_LAST; i++) {
+ o = p;
+ mask = esfilterclsmask[i];
+ if (TAILQ_EMPTY(&esfilters[i])) {
+ TAILQ_FOREACH(st, &t->s_components, es_link) {
+ if ((mask & SCT_MASK(st->es_type)) != 0)
+ service_build_filter_add(t, st, sta, &p);
+ }
+ continue;
+ }
+ exclusive = 0;
+ TAILQ_FOREACH(esf, &esfilters[i], esf_link) {
+ if (!esf->esf_enabled)
+ continue;
+ TAILQ_FOREACH(st, &t->s_components, es_link) {
+ if ((mask & SCT_MASK(st->es_type)) == 0)
+ continue;
+ if (esf->esf_type && (esf->esf_type & SCT_MASK(st->es_type)) == 0)
+ continue;
+ if (esf->esf_language[0] &&
+ strncmp(esf->esf_language, st->es_lang, 4))
+ continue;
+ if (esf->esf_service && esf->esf_service[0]) {
+ if (strcmp(esf->esf_service, idnode_uuid_as_str(&t->s_id)))
+ continue;
+ if (esf->esf_pid && esf->esf_pid != st->es_pid)
+ continue;
+ }
+ if (i == ESF_CLASS_CA &&
+ (esf->esf_caid != -1 || esf->esf_caprovider != -1)) {
+ LIST_FOREACH(ca, &st->es_caids, link) {
+ if (esf->esf_caid != -1 && ca->caid != esf->esf_caid)
+ continue;
+ if (esf->esf_caprovider != -1 && ca->providerid != esf->esf_caprovider)
+ continue;
+ break;
+ }
+ if (ca == NULL)
+ continue;
+ }
+ if (esf->esf_log)
+ tvhlog(LOG_INFO, "service", "esfilter: %s %03d %05d %s %s %s %s",
+ esfilter_class2txt(i), esf->esf_index,
+ st->es_pid, streaming_component_type2txt(st->es_type),
+ lang_code_get(st->es_lang), t->s_nicename,
+ esfilter_action2txt(esf->esf_action));
+ switch (esf->esf_action) {
+ case ESFA_NONE:
+ break;
+ case ESFA_IGNORE:
+ st->es_filter |= ESFM_IGNORE;
+ break;
+ case ESFA_USE:
+ service_build_filter_add(t, st, sta, &p);
+ break;
+ case ESFA_ONCE:
+ if (esf->esf_language[0] == '\0') {
+ service_build_filter_add(t, st, sta, &p);
+ } else {
+ int present = 0;
+ TAILQ_FOREACH(st2, &t->s_components, es_link) {
+ if ((st2->es_filter & ESFM_USED) == 0)
+ continue;
+ if (strcmp(st2->es_lang, st->es_lang) == 0) {
+ present = 1;
+ break;
+ }
+ }
+ if (!present)
+ service_build_filter_add(t, st, sta, &p);
+ }
+ break;
+ case ESFA_EXCLUSIVE:
+ break;
+ case ESFA_EMPTY:
+ if (p == o)
+ service_build_filter_add(t, st, sta, &p);
+ break;
+ default:
+ tvhlog(LOG_DEBUG, "service", "Unknown esfilter action %d", esf->esf_action);
+ break;
+ }
+ if (esf->esf_action == ESFA_EXCLUSIVE) {
+ /* forget previous work */
+ while (p > o) {
+ p--;
+ TAILQ_REMOVE(&t->s_filt_components, sta[p], es_filt_link);
+ }
+ service_build_filter_add(t, st, sta, &p);
+ exclusive = 1;
+ break;
+ }
+ }
+ }
+ if (!exclusive) {
+ TAILQ_FOREACH(st, &t->s_components, es_link) {
+ if ((mask & SCT_MASK(st->es_type)) != 0 &&
+ (st->es_filter & (ESFM_USED|ESFM_IGNORE)) == 0)
+ service_build_filter_add(t, st, sta, &p);
+ }
+ }
+ }
+}
+
/**
*
*/
@@ -336,6 +492,8 @@ service_start(service_t *t, int instance)
t->s_streaming_status = 0;
t->s_scrambled_seen = 0;
+ service_build_filter(t);
+
if((r = t->s_start_feed(t, instance)))
return r;
@@ -349,7 +507,7 @@ service_start(service_t *t, int instance)
/**
* Initialize stream
*/
- TAILQ_FOREACH(st, &t->s_components, es_link)
+ TAILQ_FOREACH(st, &t->s_filt_components, es_link)
stream_init(st);
pthread_mutex_unlock(&t->s_stream_mutex);
@@ -501,6 +659,7 @@ service_destroy(service_t *t, int delconf)
t->s_status = SERVICE_ZOMBIE;
+ TAILQ_INIT(&t->s_filt_components);
while((st = TAILQ_FIRST(&t->s_components)) != NULL)
service_stream_destroy(t, st);
@@ -552,6 +711,7 @@ service_create0
t->s_channel_name = service_channel_name;
t->s_provider_name = service_provider_name;
TAILQ_INIT(&t->s_components);
+ TAILQ_INIT(&t->s_filt_components);
t->s_last_pid = -1;
streaming_pad_init(&t->s_streaming_pad);
@@ -631,7 +791,7 @@ elementary_stream_t *
service_stream_create(service_t *t, int pid,
streaming_component_type_t type)
{
- elementary_stream_t *st;
+ elementary_stream_t *st, *st2;
int i = 0;
int idx = 0;
lock_assert(&t->s_stream_mutex);
@@ -662,8 +822,14 @@ service_stream_create(service_t *t, int pid,
if(t->s_flags & S_DEBUG)
tvhlog(LOG_DEBUG, "service", "Add stream %s", st->es_nicename);
- if(t->s_status == SERVICE_RUNNING)
- stream_init(st);
+ if(t->s_status == SERVICE_RUNNING) {
+ service_build_filter(t);
+ TAILQ_FOREACH(st2, &t->s_filt_components, es_filt_link)
+ if (st2 == st) {
+ stream_init(st);
+ break;
+ }
+ }
return st;
}
@@ -842,9 +1008,10 @@ service_restart(service_t *t, int had_components)
streaming_msg_free(sm);
}
+ service_build_filter(t);
descrambler_service_start(t);
- if(TAILQ_FIRST(&t->s_components) != NULL) {
+ if(TAILQ_FIRST(&t->s_filt_components) != NULL) {
sm = streaming_msg_create_data(SMT_START,
service_build_stream_start(t));
streaming_pad_deliver(&t->s_streaming_pad, sm);
@@ -871,7 +1038,7 @@ service_build_stream_start(service_t *t)
lock_assert(&t->s_stream_mutex);
- TAILQ_FOREACH(st, &t->s_components, es_link)
+ TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link)
n++;
ss = calloc(1, sizeof(streaming_start_t) +
@@ -880,7 +1047,7 @@ service_build_stream_start(service_t *t)
ss->ss_num_components = n;
n = 0;
- TAILQ_FOREACH(st, &t->s_components, es_link) {
+ TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
streaming_start_component_t *ssc = &ss->ss_components[n++];
ssc->ssc_index = st->es_index;
ssc->ssc_type = st->es_type;
diff --git a/src/service.h b/src/service.h
index ab53082b..2aa5b56f 100644
--- a/src/service.h
+++ b/src/service.h
@@ -35,6 +35,7 @@ struct channel;
typedef struct elementary_stream {
TAILQ_ENTRY(elementary_stream) es_link;
+ TAILQ_ENTRY(elementary_stream) es_filt_link;
int es_position;
struct service *es_service;
@@ -124,6 +125,9 @@ typedef struct elementary_stream {
/* SI section processing (horrible hack) */
void *es_section;
+ /* Filter temporary variable */
+ uint32_t es_filter;
+
} elementary_stream_t;
@@ -400,9 +404,10 @@ typedef struct service {
int s_caid;
/**
- * List of all components.
+ * List of all and filtered components.
*/
struct elementary_stream_queue s_components;
+ struct elementary_stream_queue s_filt_components;
int s_last_pid;
elementary_stream_t *s_last_es;
@@ -429,6 +434,8 @@ void service_done(void);
int service_start(service_t *t, int instance);
+void service_build_filter(service_t *t);
+
service_t *service_create0(service_t *t, const idclass_t *idc, const char *uuid, int source_type, htsmsg_t *conf);
#define service_create(t, c, u, s, m)\
diff --git a/src/subscriptions.c b/src/subscriptions.c
index 0154291b..80985199 100644
--- a/src/subscriptions.c
+++ b/src/subscriptions.c
@@ -74,7 +74,7 @@ subscription_link_service(th_subscription_t *s, service_t *t)
pthread_mutex_lock(&t->s_stream_mutex);
- if(TAILQ_FIRST(&t->s_components) != NULL) {
+ if(TAILQ_FIRST(&t->s_filt_components) != NULL) {
if(s->ths_start_message != NULL)
streaming_msg_free(s->ths_start_message);
@@ -122,7 +122,7 @@ subscription_unlink_service0(th_subscription_t *s, int reason, int stop)
streaming_target_disconnect(&t->s_streaming_pad, &s->ths_input);
if(stop &&
- TAILQ_FIRST(&t->s_components) != NULL &&
+ TAILQ_FIRST(&t->s_filt_components) != NULL &&
s->ths_state == SUBSCRIPTION_GOT_SERVICE) {
// Send a STOP message to the subscription client
sm = streaming_msg_create_code(SMT_STOP, reason);
diff --git a/src/tvheadend.h b/src/tvheadend.h
index f0601ab1..83d29fe3 100644
--- a/src/tvheadend.h
+++ b/src/tvheadend.h
@@ -220,8 +220,11 @@ typedef enum {
SCT_MP4A,
SCT_VP8,
SCT_VORBIS,
+ SCT_LAST = SCT_VORBIS
} streaming_component_type_t;
+#define SCT_MASK(t) (1 << (t))
+
#define SCT_ISVIDEO(t) ((t) == SCT_MPEG2VIDEO || (t) == SCT_H264 || \
(t) == SCT_VP8)
diff --git a/src/webui/extjs.c b/src/webui/extjs.c
index bd828bbc..ed7e5b2e 100755
--- a/src/webui/extjs.c
+++ b/src/webui/extjs.c
@@ -149,6 +149,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
extjs_load(hq, "static/app/capmteditor.js");
extjs_load(hq, "static/app/tvadapters.js");
extjs_load(hq, "static/app/idnode.js");
+ extjs_load(hq, "static/app/esfilter.js");
#if ENABLE_LINUXDVB
extjs_load(hq, "static/app/mpegts.js");
#endif
diff --git a/src/webui/static/app/esfilter.js b/src/webui/static/app/esfilter.js
new file mode 100644
index 00000000..781b46ad
--- /dev/null
+++ b/src/webui/static/app/esfilter.js
@@ -0,0 +1,108 @@
+/*
+ * Elementary Stream Filters
+ */
+
+tvheadend.esfilter_tab = function(panel)
+{
+ tvheadend.idnode_grid(panel, {
+ url : 'api/esfilter/video',
+ comet : 'esfilter_video',
+ titleS : 'Video Stream Filter',
+ titleP : 'Video Stream Filters',
+ tabIndex : 0,
+ add : {
+ url : 'api/esfilter/video',
+ create : {}
+ },
+ del : true,
+ move : true,
+ help : function() {
+ new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
+ }
+ });
+
+ tvheadend.idnode_grid(panel, {
+ url : 'api/esfilter/audio',
+ comet : 'esfilter_audio',
+ titleS : 'Audio Stream Filter',
+ titleP : 'Audio Stream Filters',
+ tabIndex : 1,
+ add : {
+ url : 'api/esfilter/audio',
+ create : {}
+ },
+ del : true,
+ move : true,
+ help : function() {
+ new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
+ }
+ });
+
+ tvheadend.idnode_grid(panel, {
+ url : 'api/esfilter/teletext',
+ comet : 'esfilter_teletext',
+ titleS : 'Teletext Stream Filter',
+ titleP : 'Teletext Stream Filters',
+ tabIndex : 2,
+ add : {
+ url : 'api/esfilter/teletext',
+ create : {}
+ },
+ del : true,
+ move : true,
+ help : function() {
+ new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
+ }
+ });
+
+ tvheadend.idnode_grid(panel, {
+ url : 'api/esfilter/subtit',
+ comet : 'esfilter_subtit',
+ titleS : 'Subtitle Stream Filter',
+ titleP : 'Subtitle Stream Filters',
+ tabIndex : 3,
+ add : {
+ url : 'api/esfilter/subtit',
+ create : {}
+ },
+ del : true,
+ move : true,
+ help : function() {
+ new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
+ }
+ });
+
+ tvheadend.idnode_grid(panel, {
+ url : 'api/esfilter/ca',
+ comet : 'esfilter_ca',
+ titleS : 'CA Stream Filter',
+ titleP : 'CA Stream Filters',
+ tabIndex : 4,
+ add : {
+ url : 'api/esfilter/ca',
+ create : {}
+ },
+ del : true,
+ move : true,
+ help : function() {
+ new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
+ }
+ });
+
+ tvheadend.idnode_grid(panel, {
+ url : 'api/esfilter/other',
+ comet : 'esfilter_other',
+ titleS : 'Other Stream Filter',
+ titleP : 'Other Stream Filters',
+ tabIndex : 5,
+ add : {
+ url : 'api/esfilter/other',
+ create : {}
+ },
+ del : true,
+ move : true,
+ help : function() {
+ new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
+ }
+ });
+}
diff --git a/src/webui/static/app/ext.css b/src/webui/static/app/ext.css
index 4152748a..810ed5df 100644
--- a/src/webui/static/app/ext.css
+++ b/src/webui/static/app/ext.css
@@ -148,6 +148,14 @@
background-image: url(../icons/delete.png) !important;
}
+.moveup {
+ background-image: url(../icons/arrow_up.png) !important;
+}
+
+.movedown {
+ background-image: url(../icons/arrow_down.png) !important;
+}
+
.save {
background-image: url(../icons/save.png) !important;
}
@@ -286,6 +294,11 @@
.arrow_switch {
background-image: url(../icons/arrow_switch.png) !important;
+
+}
+
+.stream_config {
+ background-image: url(../icons/film_edit.png) !important;
}
.x-smallhdr {
diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js
index 880565d6..8c7108d1 100644
--- a/src/webui/static/app/idnode.js
+++ b/src/webui/static/app/idnode.js
@@ -145,7 +145,6 @@ tvheadend.IdNodeField = function (conf)
width : w,
dataIndex: this.id,
header : this.text,
- sortable : true,
editor : this.editor({create: false}),
renderer : this.renderer(),
hidden : this.hidden,
@@ -665,10 +664,12 @@ tvheadend.idnode_grid = function(panel, conf)
var undoBtn = null;
var addBtn = null;
var delBtn = null;
+ var upBtn = null;
+ var downBtn = null;
var editBtn = null;
/* Model */
- var idnode = new tvheadend.IdNode(d);
+ var idnode = new tvheadend.IdNode(d);
for (var i = 0; i < idnode.length(); i++) {
var f = idnode.field(i);
var c = f.column();
@@ -708,8 +709,11 @@ tvheadend.idnode_grid = function(panel, conf)
});
/* Model */
+ var sortable = true;
+ if (conf.move)
+ sortable = false;
var model = new Ext.grid.ColumnModel({
- defaultSortable : true,
+ defaultSortable : sortable,
columns : columns
});
@@ -727,6 +731,10 @@ tvheadend.idnode_grid = function(panel, conf)
select.on('selectionchange', function(s){
if (delBtn)
delBtn.setDisabled(s.getCount() == 0);
+ if (upBtn) {
+ upBtn.setDisabled(s.getCount() == 0);
+ downBtn.setDisabled(s.getCount() == 0);
+ }
editBtn.setDisabled(s.getCount() != 1);
if (conf.selected)
conf.selected(s);
@@ -809,7 +817,59 @@ tvheadend.idnode_grid = function(panel, conf)
});
buttons.push(delBtn);
}
- if (conf.add || conf.del)
+ if (conf.move) {
+ upBtn = new Ext.Toolbar.Button({
+ tooltip : 'Move selected entries up',
+ iconCls : 'moveup',
+ text : 'Move Up',
+ disabled : true,
+ handler : function() {
+ var r = select.getSelections();
+ if (r && r.length > 0) {
+ var uuids = []
+ for ( var i = 0; i < r.length; i++ )
+ uuids.push(r[i].id)
+ Ext.Ajax.request({
+ url : 'api/idnode/moveup',
+ params : {
+ uuid: Ext.encode(uuids)
+ },
+ success : function(d)
+ {
+ store.reload();
+ }
+ });
+ }
+ }
+ });
+ buttons.push(upBtn);
+ downBtn = new Ext.Toolbar.Button({
+ tooltip : 'Move selected entries down',
+ iconCls : 'movedown',
+ text : 'Move Down',
+ disabled : true,
+ handler : function() {
+ var r = select.getSelections();
+ if (r && r.length > 0) {
+ var uuids = []
+ for ( var i = 0; i < r.length; i++ )
+ uuids.push(r[i].id)
+ Ext.Ajax.request({
+ url : 'api/idnode/movedown',
+ params : {
+ uuid: Ext.encode(uuids)
+ },
+ success : function(d)
+ {
+ store.reload();
+ }
+ });
+ }
+ }
+ });
+ buttons.push(downBtn);
+ }
+ if (conf.add || conf.del || conf.move)
buttons.push('-');
editBtn = new Ext.Toolbar.Button({
tooltip : 'Edit selected entry',
diff --git a/src/webui/static/app/mpegts.js b/src/webui/static/app/mpegts.js
index 1dedd02a..3249bbec 100644
--- a/src/webui/static/app/mpegts.js
+++ b/src/webui/static/app/mpegts.js
@@ -99,15 +99,6 @@ tvheadend.show_service_streams = function ( data ) {
var i, j;
var html = '';
- html += '';
- html += 'Index | ';
- html += 'PID | ';
- html += 'Type | ';
- html += 'Language | ';
- html += 'Details | ';
- html += '';
-
function hexstr ( d ) {
return ('0000' + d.toString(16)).slice(-4);
}
@@ -126,8 +117,23 @@ tvheadend.show_service_streams = function ( data ) {
return r;
}
- for (i = 0; i < data.streams.length; i++) {
- var s = data.streams[i];
+ function header ( ) {
+ html += '';
+ html += 'Index | ';
+ html += 'PID | ';
+ html += 'Type | ';
+ html += 'Language | ';
+ html += 'Details | ';
+ html += '';
+
+ }
+
+ function single ( s ) {
+ html += '' + s + ' |
';
+ }
+
+ function stream ( s ) {
var d = ' ';
var p = '0x' + hexstr(s.pid) + ' / ' + fixstr(s.pid);
@@ -146,16 +152,36 @@ tvheadend.show_service_streams = function ( data ) {
}
html += '' + d + ' | ';
html += '';
- }
+ }
+
+ header();
+
+ if (data.streams.length) {
+ for (i = 0; i < data.streams.length; i++)
+ stream(data.streams[i]);
+ } else
+ single('None');
+
+ single(' ');
+ single('After filtering and reordering (without PCR and PMT)
');
+ header();
+
+ if (data.fstreams.length)
+ for (i = 0; i < data.fstreams.length; i++)
+ stream(data.fstreams[i]);
+ else
+ single('None
');
var win = new Ext.Window({
title : 'Service details for ' + data.name,
layout : 'fit',
width : 650,
- height : 300,
+ height : 400,
plain : true,
bodyStyle : 'padding: 5px',
- html : html
+ html : html,
+ autoScroll: true,
+ autoShow: true
});
win.show();
}
diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js
index 36b188bf..dd9806a9 100644
--- a/src/webui/static/app/tvheadend.js
+++ b/src/webui/static/app/tvheadend.js
@@ -295,6 +295,17 @@ function accessUpdate(o) {
tabs1.push(tvheadend.conf_csa);
}
+ /* Stream Config */
+ tvheadend.conf_stream = new Ext.TabPanel({
+ activeTab: 0,
+ autoScroll: true,
+ title: 'Stream',
+ iconCls: 'stream_config',
+ items: []
+ });
+ tvheadend.esfilter_tab(tvheadend.conf_stream);
+ tabs1.push(tvheadend.conf_stream);
+
/* Debug */
tabs1.push(new tvheadend.tvhlog);
diff --git a/src/webui/static/icons/film_edit.png b/src/webui/static/icons/film_edit.png
new file mode 120000
index 00000000..03a05bfb
--- /dev/null
+++ b/src/webui/static/icons/film_edit.png
@@ -0,0 +1 @@
+../../../../vendor/famfamsilk/film_edit.png
\ No newline at end of file