subscriptions.c mux.c tsdemux.c buffer.c tcp.c \
resolver.c tsmux.c parsers.c bitstream.c parser_h264.c spawn.c
-SRCS += http.c htmlui.c
+SRCS += http.c
SRCS += htsp.c rpc.c
#
VPATH += ajaxui
-SRCS += ajaxui.c ajaxui_channels.c ajaxui_config.c
+SRCS += ajaxui.c ajaxui_channels.c \
+ ajaxui_config.c ajaxui_config_channels.c ajaxui_config_dvb.c \
+ ajaxui_config_transport.c
JSSRCS += tvheadend.js
VPATH += ajaxui/images
GIFSRCS+= sbbody_l.gif sbbody_r.gif sbhead_l.gif sbhead_r.gif
-
+PNGSRCS+= mapped.png unmapped.png
VPATH += ajaxui/prototype
JSSRCS += prototype.js
#include "obj/sbhead_l.gifh"
#include "obj/sbhead_r.gifh"
+#include "obj/mapped.pngh"
+#include "obj/unmapped.pngh"
+
const char *ajax_tabnames[] = {
}
+/**
+ *
+ */
+void
+ajax_a_jsfunc(tcp_queue_t *tq, const char *innerhtml, const char *func,
+ const char *trailer)
+{
+ tcp_qprintf(tq, "<a href=\"javascript:void(0)\" "
+ "onClick=\"javascript:%s\">%s</a>%s\r\n",
+ func, innerhtml, trailer);
+}
+
+
/*
* Titlebar AJAX page
*/
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq,
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\r\n"
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
+ /*
"<!DOCTYPE html "
"PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\r\n"
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
+ */
"\r\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" "
"xml:lang=\"en\" lang=\"en\">"
http_resource_add("/sidebox/sbhead-r.gif", embedded_sbhead_r,
sizeof(embedded_sbhead_r), "image/gif", NULL);
+ http_resource_add("/gfx/unmapped.png", embedded_unmapped,
+ sizeof(embedded_unmapped), "image/png", NULL);
+
+ http_resource_add("/gfx/mapped.png", embedded_mapped,
+ sizeof(embedded_mapped), "image/png", NULL);
+
ajax_channels_init();
ajax_config_init();
-
+ ajax_config_transport_init();
}
cursor:move;
}
+
+
+.normallist {
+ margin:0;
+ margin-top:10px;
+ padding:0;
+ list-style-type: none;
+ }
+.normallist li {
+ margin:0;
+ padding:0px;
+ }
+
/**
* Input classes
*/
background: #fff
}
+.nicebox {
+ margin: 1px;
+ padding: 0px;
+ border: 0px;
+ width: 11px;
+ height: 11px;
+}
+
+
/**
* Misc classes
*/
+.infoprefix {
+ float: left;
+ width: 50px;
+ text-align: right;
+}
+
+.infoprefixwide {
+ padding-right: 10px;
+ float: left;
+ width: 100px;
+ text-align: right;
+}
+
+.infoprefixwidefat {
+ padding-right: 10px;
+ float: left;
+ width: 130px;
+ text-align: right;
+ margin-top: 4px;
+}
+
+.pidheader {
+ float: left;
+ text-decoration: underline;
+}
+
+
+
.chgroupaction {
text-align: right;
}
+
/**
* Box with round edges
*/
void ajax_menu_bar_from_array(tcp_queue_t *tq, const char *name,
const char **vec, int num, int cur);
+void ajax_a_jsfunc(tcp_queue_t *tq, const char *innerhtml, const char *func,
+ const char *trailer);
+
int ajax_channelgroup_tab(http_connection_t *hc);
int ajax_config_tab(http_connection_t *hc);
+int ajax_config_channels_tab(http_connection_t *hc);
+void ajax_config_channels_init(void);
+
+int ajax_config_dvb_tab(http_connection_t *hc);
+void ajax_config_dvb_init(void);
+void ajax_config_transport_init(void);
+
+int ajax_transport_build_list(tcp_queue_t *tq,
+ struct th_transport_list *tlist);
+
#endif /* AJAXUI_H_ */
#define AJAX_CONFIG_TAB_CHANNELS 0
-#define AJAX_CONFIG_TAB_ADAPTERS 1
+#define AJAX_CONFIG_TAB_DVB 1
#define AJAX_CONFIG_TABS 2
const char *ajax_config_tabnames[] = {
[AJAX_CONFIG_TAB_CHANNELS] = "Channels & Groups",
- [AJAX_CONFIG_TAB_ADAPTERS] = "DVB adapters",
+ [AJAX_CONFIG_TAB_DVB] = "DVB adapters",
};
return 0;
}
-/**
- * Render a channel group widget
- */
-static void
-ajax_chgroup_build(tcp_queue_t *tq, th_channel_group_t *tcg)
-{
- tcp_qprintf(tq, "<li id=\"chgrp_%d\">", tcg->tcg_tag);
-
- ajax_box_begin(tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
-
- tcp_qprintf(tq,
- "<div %s>%s</div>",
- tcg == defgroup ? "" : "style=\"float: left\" ",
- tcg->tcg_name);
-
- if(tcg != defgroup) {
- tcp_qprintf(tq,
- "<div class=\"chgroupaction\">"
- "<a href=\"javascript:void(0)\" "
- "onClick=\"dellistentry('/ajax/chgroup_del','%d', '%s');\""
- ">Delete</a></div>",
- tcg->tcg_tag, tcg->tcg_name);
- }
-
-
- ajax_box_end(tq, AJAX_BOX_BORDER);
- tcp_qprintf(tq, "</li>");
-}
-
-/**
- * Update order of channel groups
- */
-static int
-ajax_chgroup_updateorder(http_connection_t *hc, const char *remain,
- void *opaque)
-{
- th_channel_group_t *tcg;
- tcp_queue_t tq;
- http_arg_t *ra;
-
- tcp_init_queue(&tq, -1);
-
- TAILQ_FOREACH(ra, &hc->hc_req_args, link) {
- if(strcmp(ra->key, "channelgrouplist[]") ||
- (tcg = channel_group_by_tag(atoi(ra->val))) == NULL)
- continue;
-
- TAILQ_REMOVE(&all_channel_groups, tcg, tcg_global_link);
- TAILQ_INSERT_TAIL(&all_channel_groups, tcg, tcg_global_link);
- }
-
- tcp_qprintf(&tq, "<span id=\"updatedok\">Updated on server</span>");
- ajax_js(&tq, "Effect.Fade('updatedok')");
- http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
- return 0;
-}
-
-
-
-/**
- * Add a new channel group
- */
-static int
-ajax_chgroup_add(http_connection_t *hc, const char *remain, void *opaque)
-{
- th_channel_group_t *tcg;
- tcp_queue_t tq;
- const char *name;
-
- tcp_init_queue(&tq, -1);
-
- if((name = http_arg_get(&hc->hc_req_args, "name")) != NULL) {
-
- TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link)
- if(!strcmp(name, tcg->tcg_name))
- break;
-
- if(tcg == NULL) {
- tcg = channel_group_find(name, 1);
-
- ajax_chgroup_build(&tq, tcg);
-
- /* We must recreate the Sortable object */
-
- ajax_js(&tq, "Sortable.destroy(\"channelgrouplist\")");
-
- ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
- "{onUpdate:function(){updatelistonserver("
- "'channelgrouplist', "
- "'/ajax/chgroup_updateorder', "
- "'list-info'"
- ")}});");
- }
- }
- http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
- return 0;
-}
-
-
-
-/**
- * Delete a channel group
- */
-static int
-ajax_chgroup_del(http_connection_t *hc, const char *remain, void *opaque)
-{
- th_channel_group_t *tcg;
- tcp_queue_t tq;
- const char *id;
-
- if((id = http_arg_get(&hc->hc_req_args, "id")) == NULL)
- return HTTP_STATUS_BAD_REQUEST;
-
- if((tcg = channel_group_by_tag(atoi(id))) == NULL)
- return HTTP_STATUS_BAD_REQUEST;
-
- tcp_init_queue(&tq, -1);
- tcp_qprintf(&tq, "$('chgrp_%d').remove();", tcg->tcg_tag);
- http_output_queue(hc, &tq, "text/javascript; charset=UTF-8", 0);
-
- channel_group_destroy(tcg);
-
- return 0;
-}
-
-
-
-/**
- * Channel group & channel configuration
- */
-static int
-ajax_channel_config_tab(http_connection_t *hc)
-{
- tcp_queue_t tq;
- th_channel_group_t *tcg;
-
- tcp_init_queue(&tq, -1);
-
- tcp_qprintf(&tq, "<div style=\"float: left; width: 400px\">");
-
- ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, "channelgroups",
- NULL, "Channel groups");
-
- tcp_qprintf(&tq, "<div style=\"height:15px; text-align:center\" "
- "id=\"list-info\"></div>");
-
- tcp_qprintf(&tq, "<ul id=\"channelgrouplist\" class=\"draglist\">");
-
- TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
- if(tcg->tcg_hidden)
- continue;
- ajax_chgroup_build(&tq, tcg);
- }
-
- tcp_qprintf(&tq, "</ul>");
-
- ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
- "{onUpdate:function(){updatelistonserver("
- "'channelgrouplist', "
- "'/ajax/chgroup_updateorder', "
- "'list-info'"
- ")}});");
-
- /**
- * Add new group
- */
- ajax_box_begin(&tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
-
- tcp_qprintf(&tq,
- "<div style=\"height: 20px\">"
- "<div style=\"float: left\">"
- "<input class=\"textinput\" type=\"text\" id=\"newchgrp\">"
- "</div>"
- "<div style=\"padding-top:2px\" class=\"chgroupaction\">"
- "<a href=\"javascript:void(0)\" "
- "onClick=\"javascript:addlistentry_by_widget("
- "'channelgrouplist', 'chgroup_add', 'newchgrp');\">"
- "Add</a></div>"
- "</div>");
-
- ajax_box_end(&tq, AJAX_BOX_BORDER);
-
- ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
- tcp_qprintf(&tq, "</div>");
- http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
- return 0;
-}
-
-
-
/*
* Tab AJAX page
*
switch(tab) {
case AJAX_CONFIG_TAB_CHANNELS:
- return ajax_channel_config_tab(hc);
+ return ajax_config_channels_tab(hc);
+ case AJAX_CONFIG_TAB_DVB:
+ return ajax_config_dvb_tab(hc);
default:
return HTTP_STATUS_NOT_FOUND;
void
ajax_config_init(void)
{
- http_path_add("/ajax/chgroup_add" , NULL, ajax_chgroup_add);
- http_path_add("/ajax/chgroup_del" , NULL, ajax_chgroup_del);
- http_path_add("/ajax/chgroup_updateorder", NULL, ajax_chgroup_updateorder);
http_path_add("/ajax/configmenu", NULL, ajax_config_menu);
http_path_add("/ajax/configtab", NULL, ajax_config_dispatch);
+
+ ajax_config_channels_init();
+ ajax_config_dvb_init();
}
--- /dev/null
+/*
+ * tvheadend, AJAX / HTML user interface
+ * Copyright (C) 2008 Andreas Öman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pthread.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tvhead.h"
+#include "http.h"
+#include "ajaxui.h"
+#include "channels.h"
+
+
+
+/**
+ * Render a channel group widget
+ */
+static void
+ajax_chgroup_build(tcp_queue_t *tq, th_channel_group_t *tcg)
+{
+ tcp_qprintf(tq, "<li id=\"chgrp_%d\">", tcg->tcg_tag);
+
+ ajax_box_begin(tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
+
+ tcp_qprintf(tq,
+ "<div %s>%s</div>",
+ tcg == defgroup ? "" : "style=\"float: left\" ",
+ tcg->tcg_name);
+
+ if(tcg != defgroup) {
+ tcp_qprintf(tq,
+ "<div class=\"chgroupaction\">"
+ "<a href=\"javascript:void(0)\" "
+ "onClick=\"dellistentry('/ajax/chgroup_del','%d', '%s');\""
+ ">Delete</a></div>",
+ tcg->tcg_tag, tcg->tcg_name);
+ }
+
+
+ ajax_box_end(tq, AJAX_BOX_BORDER);
+ tcp_qprintf(tq, "</li>");
+}
+
+/**
+ * Update order of channel groups
+ */
+static int
+ajax_chgroup_updateorder(http_connection_t *hc, const char *remain,
+ void *opaque)
+{
+ th_channel_group_t *tcg;
+ tcp_queue_t tq;
+ http_arg_t *ra;
+
+ tcp_init_queue(&tq, -1);
+
+ TAILQ_FOREACH(ra, &hc->hc_req_args, link) {
+ if(strcmp(ra->key, "channelgrouplist[]") ||
+ (tcg = channel_group_by_tag(atoi(ra->val))) == NULL)
+ continue;
+
+ TAILQ_REMOVE(&all_channel_groups, tcg, tcg_global_link);
+ TAILQ_INSERT_TAIL(&all_channel_groups, tcg, tcg_global_link);
+ }
+
+ tcp_qprintf(&tq, "<span id=\"updatedok\">Updated on server</span>");
+ ajax_js(&tq, "Effect.Fade('updatedok')");
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+
+
+/**
+ * Add a new channel group
+ */
+static int
+ajax_chgroup_add(http_connection_t *hc, const char *remain, void *opaque)
+{
+ th_channel_group_t *tcg;
+ tcp_queue_t tq;
+ const char *name;
+
+ tcp_init_queue(&tq, -1);
+
+ if((name = http_arg_get(&hc->hc_req_args, "name")) != NULL) {
+
+ TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link)
+ if(!strcmp(name, tcg->tcg_name))
+ break;
+
+ if(tcg == NULL) {
+ tcg = channel_group_find(name, 1);
+
+ ajax_chgroup_build(&tq, tcg);
+
+ /* We must recreate the Sortable object */
+
+ ajax_js(&tq, "Sortable.destroy(\"channelgrouplist\")");
+
+ ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
+ "{onUpdate:function(){updatelistonserver("
+ "'channelgrouplist', "
+ "'/ajax/chgroup_updateorder', "
+ "'list-info'"
+ ")}});");
+ }
+ }
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+
+
+/**
+ * Delete a channel group
+ */
+static int
+ajax_chgroup_del(http_connection_t *hc, const char *remain, void *opaque)
+{
+ th_channel_group_t *tcg;
+ tcp_queue_t tq;
+ const char *id;
+
+ if((id = http_arg_get(&hc->hc_req_args, "id")) == NULL)
+ return HTTP_STATUS_BAD_REQUEST;
+
+ if((tcg = channel_group_by_tag(atoi(id))) == NULL)
+ return HTTP_STATUS_BAD_REQUEST;
+
+ tcp_init_queue(&tq, -1);
+ tcp_qprintf(&tq, "$('chgrp_%d').remove();", tcg->tcg_tag);
+ http_output_queue(hc, &tq, "text/javascript; charset=UTF-8", 0);
+
+ channel_group_destroy(tcg);
+
+ return 0;
+}
+
+
+
+/**
+ * Channel group & channel configuration
+ */
+int
+ajax_config_channels_tab(http_connection_t *hc)
+{
+ tcp_queue_t tq;
+ th_channel_group_t *tcg;
+
+ tcp_init_queue(&tq, -1);
+
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 400px\">");
+
+ ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, "channelgroups",
+ NULL, "Channel groups");
+
+ tcp_qprintf(&tq, "<div style=\"height:15px; text-align:center\" "
+ "id=\"list-info\"></div>");
+
+ tcp_qprintf(&tq, "<ul id=\"channelgrouplist\" class=\"draglist\">");
+
+ TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
+ if(tcg->tcg_hidden)
+ continue;
+ ajax_chgroup_build(&tq, tcg);
+ }
+
+ tcp_qprintf(&tq, "</ul>");
+
+ ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
+ "{onUpdate:function(){updatelistonserver("
+ "'channelgrouplist', "
+ "'/ajax/chgroup_updateorder', "
+ "'list-info'"
+ ")}});");
+
+ /**
+ * Add new group
+ */
+ ajax_box_begin(&tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
+
+ tcp_qprintf(&tq,
+ "<div style=\"height: 20px\">"
+ "<div style=\"float: left\">"
+ "<input class=\"textinput\" type=\"text\" id=\"newchgrp\">"
+ "</div>"
+ "<div style=\"padding-top:2px\" class=\"chgroupaction\">"
+ "<a href=\"javascript:void(0)\" "
+ "onClick=\"javascript:addlistentry_by_widget("
+ "'channelgrouplist', 'chgroup_add', 'newchgrp');\">"
+ "Add</a></div>"
+ "</div>");
+
+ ajax_box_end(&tq, AJAX_BOX_BORDER);
+
+ ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
+ tcp_qprintf(&tq, "</div>");
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+/**
+ *
+ */
+void
+ajax_config_channels_init(void)
+{
+ http_path_add("/ajax/chgroup_add" , NULL, ajax_chgroup_add);
+ http_path_add("/ajax/chgroup_del" , NULL, ajax_chgroup_del);
+ http_path_add("/ajax/chgroup_updateorder", NULL, ajax_chgroup_updateorder);
+
+
+
+}
--- /dev/null
+/*
+ * tvheadend, AJAX / HTML user interface
+ * Copyright (C) 2008 Andreas Öman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <pthread.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/dvb/frontend.h>
+
+#include "tvhead.h"
+#include "http.h"
+#include "ajaxui.h"
+#include "channels.h"
+#include "dvb.h"
+#include "dvb_support.h"
+#include "dvb_muxconfig.h"
+#include "strtab.h"
+#include "psi.h"
+#include "transports.h"
+
+
+static struct strtab adapterstatus[] = {
+ { "Unconfigured", TDA_STATE_UNCONFIGURED },
+ { "Running", TDA_STATE_RUNNING },
+ { "Zombie", TDA_STATE_ZOMBIE },
+};
+
+static void
+add_option(tcp_queue_t *tq, int bol, const char *name)
+{
+ if(bol)
+ tcp_qprintf(tq, "<option>%s</option>", name);
+}
+
+
+static void
+tdmi_displayname(th_dvb_mux_instance_t *tdmi, char *buf, size_t len)
+{
+ int f = tdmi->tdmi_fe_params->frequency;
+
+ if(tdmi->tdmi_adapter->tda_fe_info->type == FE_QPSK) {
+ snprintf(buf, len, "%d kHz %s", f,
+ dvb_polarisation_to_str(tdmi->tdmi_polarisation));
+ } else {
+ snprintf(buf, len, "%d kHz", f / 1000);
+ }
+}
+
+/*
+ * Adapter summary
+ */
+static int
+ajax_adaptersummary(http_connection_t *hc, const char *remain, void *opaque)
+{
+ tcp_queue_t tq;
+ th_dvb_adapter_t *tda;
+ char dispname[20];
+
+ if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ tcp_init_queue(&tq, -1);
+
+ snprintf(dispname, sizeof(dispname), "%s", tda->tda_displayname);
+ strcpy(dispname + sizeof(dispname) - 4, "...");
+
+ ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, NULL, NULL, dispname);
+
+ tcp_qprintf(&tq, "<div class=\"infoprefix\">Status:</div>"
+ "<div>%s</div>",
+ val2str(tda->tda_state, adapterstatus) ?: "invalid");
+ tcp_qprintf(&tq, "<div class=\"infoprefix\">Type:</div>"
+ "<div>%s</div>",
+ dvb_adaptertype_to_str(tda->tda_fe_info->type));
+
+ tcp_qprintf(&tq, "<div class=\"infoprefix\">Tuner:</div>"
+ "<div>...</div>");
+ tcp_qprintf(&tq, "<div style=\"text-align: center\">"
+ "<a href=\"javascript:void(0);\" "
+ "onClick=\"new Ajax.Updater('dvbadaptereditor', "
+ "'/ajax/dvbadaptereditor/%s', "
+ "{method: 'get', evalScripts: true})\""
+ "\">Edit</a></div>", tda->tda_identifier);
+ ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
+
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+
+/**
+ * DVB configuration
+ */
+int
+ajax_config_dvb_tab(http_connection_t *hc)
+{
+ tcp_queue_t tq;
+ th_dvb_adapter_t *tda;
+
+ tcp_init_queue(&tq, -1);
+
+ tcp_qprintf(&tq, "<div style=\"overflow: auto; width: 100%\">");
+
+ LIST_FOREACH(tda, &dvb_adapters, tda_global_link) {
+
+ tcp_qprintf(&tq, "<div id=\"summary_%s\" "
+ "style=\"float:left; width: 250px\"></div>",
+ tda->tda_identifier);
+
+ ajax_js(&tq, "new Ajax.Updater('summary_%s', "
+ "'/ajax/dvbadaptersummary/%s', {method: 'get'})",
+ tda->tda_identifier, tda->tda_identifier);
+
+ }
+ tcp_qprintf(&tq, "</div>");
+ tcp_qprintf(&tq, "<div id=\"dvbadaptereditor\"></div>");
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+/**
+ * Generate the 'add new...' mux link
+ *
+ * if result is set we add a fade out of a result from a previous op
+ */
+static void
+dvb_make_add_link(tcp_queue_t *tq, th_dvb_adapter_t *tda, const char *result)
+{
+ tcp_qprintf(tq,
+ "<p><a href=\"javascript:void(0);\" "
+ "onClick=\"new Ajax.Updater('addmux', "
+ "'/ajax/dvbadapteraddmux/%s', {method: 'get'})\""
+ ">Add new...</a></p>", tda->tda_identifier);
+
+ if(result) {
+ tcp_qprintf(tq, "<div id=\"result\">%s</div>", result);
+ ajax_js(tq, "Effect.Fade('result')");
+ }
+}
+
+
+/**
+ * DVB adapter editor pane
+ */
+static int
+ajax_adaptereditor(http_connection_t *hc, const char *remain, void *opaque)
+{
+ tcp_queue_t tq;
+ th_dvb_adapter_t *tda;
+ float a, b, c;
+
+ if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ tcp_init_queue(&tq, -1);
+
+ ajax_box_begin(&tq, AJAX_BOX_FILLED, NULL, NULL, NULL);
+
+ tcp_qprintf(&tq,
+ "<div style=\"text-align: center; font-weight: bold\">%s</div>",
+ tda->tda_displayname);
+
+ ajax_box_end(&tq, AJAX_BOX_FILLED);
+
+ /* Type */
+
+ tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Model:</div>"
+ "<div>%s (%s)</div>",
+ tda->tda_fe_info->name,
+ dvb_adaptertype_to_str(tda->tda_fe_info->type));
+
+
+ switch(tda->tda_fe_info->type) {
+ case FE_QPSK:
+ a = tda->tda_fe_info->frequency_min;
+ b = tda->tda_fe_info->frequency_max;
+ c = tda->tda_fe_info->frequency_stepsize;
+ break;
+
+ default:
+ a = tda->tda_fe_info->frequency_min / 1000.0f;
+ b = tda->tda_fe_info->frequency_max / 1000.0f;
+ c = tda->tda_fe_info->frequency_stepsize / 1000.0f;
+ break;
+ }
+
+ tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Freq. Range:</div>"
+ "<div>%.2f - %.2f kHz, in steps of %.2f kHz</div>",
+ a, b, c);
+
+
+ if(tda->tda_fe_info->symbol_rate_min) {
+ tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Symbolrate:</div>"
+ "<div>%d - %d BAUD</div>",
+ tda->tda_fe_info->symbol_rate_min,
+ tda->tda_fe_info->symbol_rate_max);
+ }
+
+
+ /* Capabilities */
+
+
+ // tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Capabilities:</div>");
+
+ tcp_qprintf(&tq, "<div style=\"float: left; width:45%\">");
+ ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, NULL, NULL, "Multiplexes");
+
+ /* List of muxes */
+
+ tcp_qprintf(&tq, "<div style=\"overflow: auto; width: 100%\">");
+
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 20%\">"
+ "Freq.</div>");
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
+ "Status");
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%s</div>",
+ "State");
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
+ "Name");
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%s</div>",
+ "Services");
+ tcp_qprintf(&tq, "</div><hr>");
+
+
+ tcp_qprintf(&tq, "<div id=\"dvbmuxlist%s\" class=\"normallist\">",
+ tda->tda_identifier);
+ tcp_qprintf(&tq, "</div>");
+
+
+
+ ajax_js(&tq,
+ "new Ajax.PeriodicalUpdater('dvbmuxlist%s', "
+ "'/ajax/dvbadaptermuxlist/%s', {method: 'get', frequency: 5}) ",
+ tda->tda_identifier, tda->tda_identifier);
+
+ tcp_qprintf(&tq, "<hr><div id=\"addmux\">");
+ dvb_make_add_link(&tq, tda, NULL);
+ tcp_qprintf(&tq, "</div>");
+
+ ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
+ tcp_qprintf(&tq, "</div>");
+
+ /* Div for displaying services */
+
+ tcp_qprintf(&tq, "<div id=\"servicepane\" "
+ "style=\"float: left; width:55%\">");
+ tcp_qprintf(&tq, "</div>");
+
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+
+/**
+ * DVB adapter add mux
+ */
+static int
+ajax_adapteraddmux(http_connection_t *hc, const char *remain, void *opaque)
+{
+ tcp_queue_t tq;
+ th_dvb_adapter_t *tda;
+ int caps;
+ int fetype;
+ char params[400];
+
+ if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ caps = tda->tda_fe_info->caps;
+ fetype = tda->tda_fe_info->type;
+
+ tcp_init_queue(&tq, -1);
+
+ tcp_qprintf(&tq, "<div style=\"text-align: center; font-weight: bold\">"
+ "Add new %s mux</div>",
+ dvb_adaptertype_to_str(tda->tda_fe_info->type));
+
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">Frequency (%s):</div>"
+ "<div>"
+ "<input class=\"textinput\" type=\"text\" id=\"freq\">"
+ "</div>",
+ fetype == FE_QPSK ? "kHz" : "Hz");
+
+ snprintf(params, sizeof(params),
+ "freq: $F('freq')");
+
+
+ /* Symbolrate */
+
+ if(fetype == FE_QAM || fetype == FE_QPSK) {
+
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">Symbolrate:</div>"
+ "<div>"
+ "<input class=\"textinput\" type=\"text\" id=\"symrate\">"
+ "</div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", symrate: $F('symrate')");
+ }
+
+ /* Bandwidth */
+
+ if(fetype == FE_OFDM) {
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">Bandwidth:</div>"
+ "<div><select id=\"bw\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_BANDWIDTH_AUTO, "AUTO");
+ add_option(&tq, 1 , "8MHz");
+ add_option(&tq, 1 , "7MHz");
+ add_option(&tq, 1 , "6MHz");
+ tcp_qprintf(&tq, "</select></div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", bw: $F('bw')");
+ }
+
+
+ /* Constellation */
+
+ if(fetype == FE_QAM || fetype == FE_OFDM) {
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">Constellation:</div>"
+ "<div><select id=\"const\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_QAM_AUTO, "AUTO");
+ add_option(&tq, caps & FE_CAN_QPSK, "QPSK");
+ add_option(&tq, caps & FE_CAN_QAM_16, "QAM16");
+ add_option(&tq, caps & FE_CAN_QAM_32, "QAM32");
+ add_option(&tq, caps & FE_CAN_QAM_64, "QAM64");
+ add_option(&tq, caps & FE_CAN_QAM_128, "QAM128");
+ add_option(&tq, caps & FE_CAN_QAM_256, "QAM256");
+
+ tcp_qprintf(&tq, "</select></div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", const: $F('const')");
+ }
+
+
+ /* FEC */
+
+ if(fetype == FE_QAM || fetype == FE_QPSK) {
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">FEC:</div>"
+ "<div><select id=\"fec\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_FEC_AUTO, "AUTO");
+ add_option(&tq, caps & FE_CAN_FEC_1_2, "1/2");
+ add_option(&tq, caps & FE_CAN_FEC_2_3, "2/3");
+ add_option(&tq, caps & FE_CAN_FEC_3_4, "3/4");
+ add_option(&tq, caps & FE_CAN_FEC_4_5, "4/5");
+ add_option(&tq, caps & FE_CAN_FEC_5_6, "5/6");
+ add_option(&tq, caps & FE_CAN_FEC_6_7, "6/7");
+ add_option(&tq, caps & FE_CAN_FEC_7_8, "7/8");
+ add_option(&tq, caps & FE_CAN_FEC_8_9, "8/9");
+ tcp_qprintf(&tq, "</select></div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", fec: $F('fec')");
+ }
+
+
+ if(fetype == FE_OFDM) {
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">Transmission mode:</div>"
+ "<div><select id=\"tmode\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_TRANSMISSION_MODE_AUTO, "AUTO");
+ add_option(&tq, 1 , "2k");
+ add_option(&tq, 1 , "8k");
+ tcp_qprintf(&tq, "</select></div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", tmode: $F('tmode')");
+
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">Guard interval:</div>"
+ "<div><select id=\"guard\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_GUARD_INTERVAL_AUTO, "AUTO");
+ add_option(&tq, 1 , "1/32");
+ add_option(&tq, 1 , "1/16");
+ add_option(&tq, 1 , "1/8");
+ add_option(&tq, 1 , "1/4");
+ tcp_qprintf(&tq, "</select></div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", guard: $F('guard')");
+
+
+
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">Hierarchy:</div>"
+ "<div><select id=\"hier\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_HIERARCHY_AUTO, "AUTO");
+ add_option(&tq, 1 , "1");
+ add_option(&tq, 1 , "2");
+ add_option(&tq, 1 , "4");
+ add_option(&tq, 1 , "NONE");
+ tcp_qprintf(&tq, "</select></div>");
+
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", hier: $F('hier')");
+
+
+
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">FEC Hi:</div>"
+ "<div><select id=\"fechi\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_FEC_AUTO, "AUTO");
+ add_option(&tq, caps & FE_CAN_FEC_1_2, "1/2");
+ add_option(&tq, caps & FE_CAN_FEC_2_3, "2/3");
+ add_option(&tq, caps & FE_CAN_FEC_3_4, "3/4");
+ add_option(&tq, caps & FE_CAN_FEC_4_5, "4/5");
+ add_option(&tq, caps & FE_CAN_FEC_5_6, "5/6");
+ add_option(&tq, caps & FE_CAN_FEC_6_7, "6/7");
+ add_option(&tq, caps & FE_CAN_FEC_7_8, "7/8");
+ add_option(&tq, caps & FE_CAN_FEC_8_9, "8/9");
+ tcp_qprintf(&tq, "</select></div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", fechi: $F('fechi')");
+
+
+ tcp_qprintf(&tq,
+ "<div class=\"infoprefixwidefat\">FEC Low:</div>"
+ "<div><select id=\"feclo\" class=\"textinput\">");
+
+ add_option(&tq, caps & FE_CAN_FEC_AUTO, "AUTO");
+ add_option(&tq, caps & FE_CAN_FEC_1_2, "1/2");
+ add_option(&tq, caps & FE_CAN_FEC_2_3, "2/3");
+ add_option(&tq, caps & FE_CAN_FEC_3_4, "3/4");
+ add_option(&tq, caps & FE_CAN_FEC_4_5, "4/5");
+ add_option(&tq, caps & FE_CAN_FEC_5_6, "5/6");
+ add_option(&tq, caps & FE_CAN_FEC_6_7, "6/7");
+ add_option(&tq, caps & FE_CAN_FEC_7_8, "7/8");
+ add_option(&tq, caps & FE_CAN_FEC_8_9, "8/9");
+ tcp_qprintf(&tq, "</select></div>");
+
+ snprintf(params + strlen(params), sizeof(params) - strlen(params),
+ ", feclo: $F('feclo')");
+ }
+
+ tcp_qprintf(&tq,
+ "<div style=\"text-align: center\">"
+ "<input type=\"button\" value=\"Create\" "
+ "onClick=\"new Ajax.Updater('addmux', "
+ "'/ajax/dvbadaptercreatemux/%s', "
+ "{evalScripts: true, parameters: {%s}})"
+ "\">"
+ "</div>", tda->tda_identifier, params);
+
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+/**
+ *
+ */
+static int
+ajax_adaptercreatemux_fail(http_connection_t *hc, th_dvb_adapter_t *tda,
+ const char *errmsg)
+{
+ tcp_queue_t tq;
+ tcp_init_queue(&tq, -1);
+ dvb_make_add_link(&tq, tda, errmsg);
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+/**
+ * DVB adapter create mux (come here on POST after addmux query)
+ */
+static int
+ajax_adaptercreatemux(http_connection_t *hc, const char *remain, void *opaque)
+{
+ tcp_queue_t tq;
+ th_dvb_adapter_t *tda;
+ const char *v;
+
+ if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ v = dvb_mux_create_str(tda,
+ http_arg_get(&hc->hc_req_args, "freq"),
+ http_arg_get(&hc->hc_req_args, "symrate"),
+ http_arg_get(&hc->hc_req_args, "const"),
+ http_arg_get(&hc->hc_req_args, "fec"),
+ http_arg_get(&hc->hc_req_args, "fechi"),
+ http_arg_get(&hc->hc_req_args, "feclo"),
+ http_arg_get(&hc->hc_req_args, "bw"),
+ http_arg_get(&hc->hc_req_args, "tmode"),
+ http_arg_get(&hc->hc_req_args, "guard"),
+ http_arg_get(&hc->hc_req_args, "hier"),
+ http_arg_get(&hc->hc_req_args, "pol"),
+ http_arg_get(&hc->hc_req_args, "port"), 1);
+
+ if(v != NULL)
+ return ajax_adaptercreatemux_fail(hc, tda, v);
+
+ tcp_init_queue(&tq, -1);
+ dvb_make_add_link(&tq, tda, "Successfully created");
+
+ ajax_js(&tq,
+ "new Ajax.Updater('dvbmuxlist%s', "
+ "'/ajax/dvbadaptermuxlist/%s', {method: 'get'}) ",
+ tda->tda_identifier, tda->tda_identifier);
+
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+
+/**
+ * Return a list of all muxes on the given adapter
+ */
+static int
+ajax_adaptermuxlist(http_connection_t *hc, const char *remain, void *opaque)
+{
+ th_dvb_mux_instance_t *tdmi;
+ tcp_queue_t tq;
+ th_dvb_adapter_t *tda;
+ char buf[50];
+ const char *txt;
+ int fetype, n;
+ th_transport_t *t;
+ int o = 1;
+
+ if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ fetype = tda->tda_fe_info->type;
+
+ tcp_init_queue(&tq, -1);
+
+ if(LIST_FIRST(&tda->tda_muxes) == NULL) {
+ tcp_qprintf(&tq, "<div style=\"text-align: center\">"
+ "No muxes configured</div>");
+ } else LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
+
+ tcp_qprintf(&tq, "<div%s>", o ? " style=\"background: #fff\"" : "");
+ o = !o;
+
+ tdmi_displayname(tdmi, buf, sizeof(buf));
+
+ tcp_qprintf(&tq, "<div style=\"overflow: auto; width: 100%\">");
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 20%\">"
+ "<a href=\"javascript:void(0);\" "
+ "onClick=\"new Ajax.Updater('servicepane', "
+ "'/ajax/dvbmuxeditor/%s', "
+ "{method: 'get', evalScripts: true})\""
+ ">%s</a></div>", tdmi->tdmi_identifier, buf);
+
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
+ dvb_mux_status(tdmi));
+
+ switch(tdmi->tdmi_state) {
+ case TDMI_IDLE: txt = "Idle"; break;
+ case TDMI_IDLESCAN: txt = "Scanning"; break;
+ case TDMI_RUNNING: txt = "Running"; break;
+ default: txt = "???"; break;
+ }
+
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%s</div>",
+ txt);
+
+ txt = tdmi->tdmi_network;
+ if(txt == NULL)
+ txt = "Unknown";
+
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
+ txt);
+
+ n = 0;
+ LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link)
+ n++;
+
+ tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%d</div>", n);
+
+ tcp_qprintf(&tq, "</div></div>");
+ }
+
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+/**
+ * Display detailes about a mux
+ */
+static int
+ajax_dvbmuxeditor(http_connection_t *hc, const char *remain, void *opaque)
+{
+ th_dvb_mux_instance_t *tdmi;
+ tcp_queue_t tq;
+ char buf[1000];
+ th_transport_t *t;
+ struct th_transport_list head;
+
+ if(remain == NULL || (tdmi = dvb_mux_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ tcp_init_queue(&tq, -1);
+
+ tdmi_displayname(tdmi, buf, sizeof(buf));
+
+ LIST_INIT(&head);
+
+ LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
+ if(transport_servicetype_txt(t) == NULL)
+ continue;
+ LIST_INSERT_HEAD(&head, t, tht_tmp_link);
+ }
+
+ ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, NULL, NULL, buf);
+ ajax_transport_build_list(&tq, &head);
+ ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
+
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ return 0;
+}
+
+
+/**
+ *
+ */
+void
+ajax_config_dvb_init(void)
+{
+ http_path_add("/ajax/dvbadaptermuxlist" , NULL, ajax_adaptermuxlist);
+ http_path_add("/ajax/dvbadaptersummary" , NULL, ajax_adaptersummary);
+ http_path_add("/ajax/dvbadaptereditor", NULL, ajax_adaptereditor);
+ http_path_add("/ajax/dvbadapteraddmux", NULL, ajax_adapteraddmux);
+ http_path_add("/ajax/dvbadaptercreatemux", NULL, ajax_adaptercreatemux);
+ http_path_add("/ajax/dvbmuxeditor", NULL, ajax_dvbmuxeditor);
+
+}
--- /dev/null
+/*
+ * tvheadend, AJAX / HTML user interface
+ * Copyright (C) 2008 Andreas Öman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <pthread.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/dvb/frontend.h>
+
+#include "tvhead.h"
+#include "http.h"
+#include "ajaxui.h"
+#include "channels.h"
+#include "strtab.h"
+#include "psi.h"
+#include "transports.h"
+
+
+/**
+ *
+ */
+int
+ajax_transport_build_list(tcp_queue_t *tq, struct th_transport_list *tlist)
+{
+ char buf[1000];
+ th_transport_t *t;
+ const char *v;
+ int o = 1;
+ th_stream_t *st;
+ const char *extra;
+
+
+ tcp_qprintf(tq, "<script type=\"text/javascript\">\r\n"
+ "//<![CDATA[\r\n");
+
+ /* Select all */
+ tcp_qprintf(tq, "select_all = function() {\r\n");
+ LIST_FOREACH(t, tlist, tht_tmp_link) {
+ tcp_qprintf(tq,
+ "document.getElementById('sel_%s').checked = true;\r\n",
+ t->tht_identifier);
+ }
+ tcp_qprintf(tq, "}\r\n");
+
+ /* Select none */
+ tcp_qprintf(tq, "select_none = function() {\r\n");
+ LIST_FOREACH(t, tlist, tht_tmp_link) {
+ tcp_qprintf(tq,
+ "document.getElementById('sel_%s').checked = false;\r\n",
+ t->tht_identifier);
+ }
+ tcp_qprintf(tq, "}\r\n");
+
+ /* Invert selection */
+ tcp_qprintf(tq, "select_invert = function() {\r\n");
+ LIST_FOREACH(t, tlist, tht_tmp_link) {
+ tcp_qprintf(tq,
+ "document.getElementById('sel_%s').checked = !"
+ "document.getElementById('sel_%s').checked;\r\n",
+ t->tht_identifier, t->tht_identifier);
+ }
+ tcp_qprintf(tq, "}\r\n");
+
+ /* Select TV transports */
+ tcp_qprintf(tq, "select_tv = function() {\r\n");
+ LIST_FOREACH(t, tlist, tht_tmp_link) {
+ tcp_qprintf(tq,
+ "document.getElementById('sel_%s').checked = %s;\r\n",
+ t->tht_identifier,
+ transport_is_tv(t) ? "true" : "false");
+ }
+ tcp_qprintf(tq, "}\r\n");
+
+ /* Select unscrambled TV transports */
+ tcp_qprintf(tq, "select_tv_nocrypt = function() {\r\n");
+ LIST_FOREACH(t, tlist, tht_tmp_link) {
+ tcp_qprintf(tq,
+ "document.getElementById('sel_%s').checked = %s;\r\n",
+ t->tht_identifier,
+ transport_is_tv(t) && !t->tht_scrambled ? "true" : "false");
+ }
+ tcp_qprintf(tq, "}\r\n");
+
+ /* Perform the given op on all transprots */
+ tcp_qprintf(tq, "selected_do = function(op) {\r\n");
+ LIST_FOREACH(t, tlist, tht_tmp_link) {
+ tcp_qprintf(tq,
+ "if(document.getElementById('sel_%s').checked) {\r\n"
+ " new Ajax.Request('/ajax/transport_op/%s', "
+ "{parameters: {action: op}});\r\n}\r\n",
+ t->tht_identifier, t->tht_identifier);
+ }
+ tcp_qprintf(tq, "}\r\n");
+
+
+ tcp_qprintf(tq,
+ "\r\n//]]>\r\n"
+ "</script>\r\n");
+
+ /* Top */
+
+ tcp_qprintf(tq, "<form id=\"transports\">");
+
+ tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 8%\"> </div>");
+ tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">SID</div>");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 10%\">Crypto</div>");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 12%\">Type</div>");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 25%\">"
+ "Service</div>");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 6%\"> </div>");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 25%\">"
+ "Channel</div>");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 6%\">"
+ "Bah</div>");
+
+ tcp_qprintf(tq, "</div><hr>");
+
+
+ LIST_FOREACH(t, tlist, tht_tmp_link) {
+ tcp_qprintf(tq, "<div%s>", o ? " style=\"background: #fff\"" : "");
+ o = !o;
+
+ tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">"
+ "<a id=\"toggle_svcinfo%s\" href=\"javascript:void(0)\" "
+ "onClick=\"showhide('svcinfo%s')\" >"
+ "More</a></div>", t->tht_identifier, t->tht_identifier);
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">%d</div>",
+ t->tht_dvb_service_id);
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 10%\">%s</div>",
+ t->tht_scrambled ? "CSA" : "Free");
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 12%\">%s</div>",
+ transport_servicetype_txt(t));
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 25%\">%s</div>",
+ t->tht_servicename ?: "");
+
+ tcp_qprintf(tq,
+ "<div style=\"float: left; width: 6%; text-align: center\">"
+ "<a href=\"javascript:void(0)\" "
+ "onClick=\"new "
+ "Ajax.Request('/ajax/transport_op/%s', "
+ "{parameters: {action: 'toggle'}})\">"
+ "<img id=\"map%s\" src=\"/gfx/unmapped.png\"></a></div>",
+ t->tht_identifier, t->tht_identifier);
+
+ tcp_qprintf(tq, "<div id=\"chname%s\" style=\"float: left; width: 25%\">",
+ t->tht_identifier);
+
+ if(t->tht_channel == NULL) {
+ /* Unmapped */
+
+ v = t->tht_channelname;
+
+ snprintf(buf, sizeof(buf),
+ "tentative_chname('chname%s', "
+ "'/ajax/transport_rename_channel/%s', '%s')",
+ t->tht_identifier, t->tht_identifier, v);
+
+ ajax_a_jsfunc(tq, v, buf, "");
+ } else {
+ /* Mapped */
+ tcp_qprintf(tq, "%s", t->tht_channel->ch_name);
+ }
+ tcp_qprintf(tq, "</div>");
+
+
+
+ tcp_qprintf(tq, "<div style=\"float: left; width: 6%\">"
+ "<input id=\"sel_%s\" type=\"checkbox\" class=\"nicebox\">"
+ "</div>", t->tht_identifier);
+
+ tcp_qprintf(tq, "</div>");
+
+ /* Extra info */
+ tcp_qprintf(tq, "<div name=\"id=\"svcinfo%s\" style=\"display: none\">",
+ t->tht_identifier);
+
+ tcp_qprintf(tq, "<p>");
+
+ tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
+ tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">"
+ " </div>");
+ tcp_qprintf(tq, "<div class=\"pidheader\" style=\"width: 8%\">"
+ "PID</div>");
+
+ tcp_qprintf(tq, "<div class=\"pidheader\" style=\"width: 22%\">"
+ "Payload</div>");
+
+ tcp_qprintf(tq, "<div class=\"pidheader\" style=\"width: 62%\">Details"
+ "</div>");
+ tcp_qprintf(tq, "</div>");
+
+
+ LIST_FOREACH(st, &t->tht_streams, st_link) {
+ tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
+ tcp_qprintf(tq, "<div style=\"float: left; width: 8%\"> </div>");
+ tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">%d</div>",
+ st->st_pid);
+ tcp_qprintf(tq, "<div style=\"float: left; width: 22%\">%s</div>",
+ htstvstreamtype2txt(st->st_type));
+
+ switch(st->st_type) {
+ case HTSTV_MPEG2AUDIO:
+ case HTSTV_AC3:
+ extra = st->st_lang;
+ break;
+ case HTSTV_CA:
+ extra = psi_caid2name(st->st_caid);
+ break;
+ default:
+ extra = NULL;
+ break;
+ }
+
+ if(extra != NULL)
+ tcp_qprintf(tq, "<div style=\"float: left; width: 62%\">%s</div>",
+ extra);
+
+ tcp_qprintf(tq, "</div>");
+ }
+
+ tcp_qprintf(tq, "</p></div>");
+ tcp_qprintf(tq, "</div>\r\n");
+
+ }
+ tcp_qprintf(tq, "<hr>\r\n");
+
+ tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
+
+ tcp_qprintf(tq, "<div class=\"infoprefix\">Select:</div><div>");
+
+ ajax_a_jsfunc(tq, "All", "select_all();", " / ");
+ ajax_a_jsfunc(tq, "None", "select_none();", " / ");
+ ajax_a_jsfunc(tq, "Invert", "select_invert();", " / ");
+ ajax_a_jsfunc(tq, "All TV-services", "select_tv();", " / ");
+ ajax_a_jsfunc(tq, "All uncrypted TV-services", "select_tv_nocrypt();", "");
+
+ tcp_qprintf(tq, "</div></div>\r\n");
+
+ tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
+ tcp_qprintf(tq, "<div class=\"infoprefix\"> </div><div>");
+
+ ajax_a_jsfunc(tq, "Map selected", "selected_do('map');", " / ");
+ ajax_a_jsfunc(tq, "Unmap selected", "selected_do('unmap');", "");
+
+ tcp_qprintf(tq, "</div></div>");
+
+ tcp_qprintf(tq, "</form>");
+ return 0;
+}
+
+/**
+ * Rename of unmapped channel
+ */
+static int
+ajax_transport_rename_channel(http_connection_t *hc,
+ const char *remain, void *opaque)
+{
+ th_transport_t *t;
+ const char *newname, *v;
+ tcp_queue_t tq;
+ char buf[1000];
+
+ if(remain == NULL || (t = transport_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ if((newname = http_arg_get(&hc->hc_req_args, "newname")) == NULL)
+ return HTTP_STATUS_BAD_REQUEST;
+
+ free((void *)t->tht_channelname);
+ t->tht_channelname = strdup(newname);
+
+ tcp_init_queue(&tq, -1);
+
+ v = newname;
+
+ snprintf(buf, sizeof(buf),
+ "tentative_chname('chname%s', "
+ "'/ajax/transport_rename_channel/%s', '%s')",
+ t->tht_identifier, t->tht_identifier, v);
+
+ ajax_a_jsfunc(&tq, v, buf, "");
+
+ http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
+ t->tht_config_change(t);
+ return 0;
+}
+
+/**
+ *
+ */
+static void
+dvb_map_channel(th_transport_t *t, tcp_queue_t *tq)
+{
+ transport_set_channel(t, t->tht_channelname);
+
+ printf("Mapped transport %s to channel %s\n",
+ t->tht_servicename, t->tht_channel->ch_name);
+
+ tcp_qprintf(tq,
+ "document.getElementById('chname%s').innerHTML='%s';\n\r"
+ "document.getElementById('map%s').src='/gfx/mapped.png';\n\r",
+ t->tht_identifier, t->tht_channel->ch_name,
+ t->tht_identifier);
+}
+
+
+/**
+ *
+ */
+static void
+dvb_unmap_channel(th_transport_t *t, tcp_queue_t *tq)
+{
+ transport_unset_channel(t);
+
+ printf("Unmapped transport %s\n", t->tht_servicename);
+
+ tcp_qprintf(tq,
+ "document.getElementById('chname%s').innerHTML='"
+ "<a href=\"javascript:void(0)\" "
+ "onClick=\"javascript:tentative_chname(\\'chname%s\\', "
+ "\\'/ajax/transport_rename_channel/%s\\', \\'%s\\')\">%s</a>"
+ "';\n\r"
+ "document.getElementById('map%s').src='/gfx/unmapped.png';\n\r",
+ t->tht_identifier, t->tht_identifier, t->tht_identifier,
+ t->tht_channelname, t->tht_channelname, t->tht_identifier);
+}
+
+
+
+/**
+ *
+ */
+int
+ajax_transport_op(http_connection_t *hc, const char *remain, void *opaque)
+{
+ th_transport_t *t;
+ tcp_queue_t tq;
+ const char *op;
+
+ if(remain == NULL || (t = transport_find_by_identifier(remain)) == NULL)
+ return HTTP_STATUS_NOT_FOUND;
+
+ if((op = http_arg_get(&hc->hc_req_args, "action")) == NULL)
+ return HTTP_STATUS_BAD_REQUEST;
+
+ tcp_init_queue(&tq, -1);
+
+ if(!strcmp(op, "toggle")) {
+ if(t->tht_channel)
+ dvb_unmap_channel(t, &tq);
+ else
+ dvb_map_channel(t, &tq);
+ } else if(!strcmp(op, "map") && t->tht_channel == NULL) {
+ dvb_map_channel(t, &tq);
+ } else if(!strcmp(op, "unmap") && t->tht_channel != NULL) {
+ dvb_unmap_channel(t, &tq);
+ }
+
+ http_output_queue(hc, &tq, "text/javascript; charset=UTF-8", 0);
+
+ t->tht_config_change(t);
+ return 0;
+}
+
+
+/**
+ *
+ */
+void
+ajax_config_transport_init(void)
+{
+ http_path_add("/ajax/transport_rename_channel", NULL,
+ ajax_transport_rename_channel);
+
+ http_path_add("/ajax/transport_op", NULL,
+ ajax_transport_op);
+}
options = this.options;
if (!options.evalScripts) responseText = responseText.stripScripts();
+ if (!$(receiver)) this.stop();
if (receiver = $(receiver)) {
if (options.insertion) {
function switchtab(name, index)
{
a = new Ajax.Updater(name + 'deck', '/ajax/' + name + 'tab/' + index,
- { asynchronous: true,
- method: 'get',
- evalScripts: true});
+ { method: 'get', evalScripts: true});
a = new Ajax.Updater(name + 'menu', '/ajax/' + name + 'menu/' + index,
- { asynchronous: true,
- method: 'get',
- evalScripts: true});
+ { method: 'get', evalScripts: true});
};
function updatelistonserver(listid, url, resultid)
// document.getElementById(resultid).innerHTML = "Updating...";
a = new Ajax.Updater(resultid, url,
- { asynchronous: true,
- evalScripts: true,
+ { evalScripts: true,
parameters:Sortable.serialize(listid)});
};
alert("Emtpy name is not allowed");
} else {
a = new Ajax.Updater(listid, url,
- { asynchronous: true,
- evalScripts: true,
+ { evalScripts: true,
parameters: { name: name },
insertion: Insertion.Bottom
});
name = $F(widget);
$(widget).clear();
addlistentry(listid, url, name);
-}
\ No newline at end of file
+}
+
+
+function showhide(name)
+{
+ ctrlname = 'toggle_' + name;
+ if(document.getElementById(ctrlname).innerHTML == 'More') {
+ document.getElementById(ctrlname).innerHTML = 'Less';
+ new Effect.Appear(name, {duration: 0.5});
+ } else {
+ document.getElementById(ctrlname).innerHTML = 'More';
+ new Effect.Fade(name, {duration: 0.5});
+ }
+}
+
+function tentative_chname(id, url, name)
+{
+ var newname = prompt("Enter name of channel", name);
+ if(newname != null && newname != name) {
+ a = new Ajax.Updater(id, url,
+ { evalScripts: true,
+ parameters: { newname: newname }});
+ }
+}
ch = channel_find("Test PAL", 1, channel_group_find("Test channels", 1));
t = calloc(1, sizeof(th_transport_t));
- t->tht_prio = 100;
t->tht_type = TRANSPORT_AVGEN;
t->tht_start_feed = avgen_start_feed;
t->tht_provider = strdup("HTS Tvheadend");
t->tht_network = strdup("Internal");
- t->tht_uniquename = strdup("TEST1");
+ t->tht_identifier = strdup("test1");
- transport_link(t, ch, THT_OTHER);
+ transport_set_channel(t, ch->ch_name);
}
static void channel_settings_write(th_channel_t *ch);
struct th_channel_list channels;
-struct th_transport_list all_transports;
int nchannels;
struct th_channel_group_queue all_channel_groups;
t = calloc(1, sizeof(th_transport_t));
- t->tht_prio = atoi(config_get_str_sub(head, "prio", ""));
-
if(0) {
#ifdef ENABLE_INPUT_IPTV
} else if((v = config_get_str_sub(head, "iptv", NULL)) != NULL) {
return NULL;
}
-void
-channel_group_move_next(th_channel_group_t *tcg)
-{
- th_channel_group_t *n = TAILQ_NEXT(tcg, tcg_global_link);
- if(n == NULL)
- return;
-
- TAILQ_REMOVE(&all_channel_groups, tcg, tcg_global_link);
- TAILQ_INSERT_AFTER(&all_channel_groups, n, tcg, tcg_global_link);
-
- channel_group_settings_write();
-}
-
-void
-channel_group_move_prev(th_channel_group_t *tcg)
-{
- th_channel_group_t *p = TAILQ_PREV(tcg, th_channel_group_queue,
- tcg_global_link);
- if(p == NULL)
- return;
-
- TAILQ_REMOVE(&all_channel_groups, tcg, tcg_global_link);
- TAILQ_INSERT_BEFORE(p, tcg, tcg_global_link);
- channel_group_settings_write();
-}
-
/**
* Write out a config file with all channel groups
if(ct->ct_keystate != CT_FORBIDDEN)
syslog(LOG_ERR,
"Cannot descramble \"%s\" for channel \"%s\", access denied",
- t->tht_uniquename, t->tht_channel->ch_name);
+ t->tht_identifier, t->tht_channel->ch_name);
ct->ct_keystate = CT_FORBIDDEN;
return 0;
if(ct->ct_keystate != CT_RESOLVED)
syslog(LOG_INFO,
"Obtained key for \"%s\" for channel \"%s\"",
- t->tht_uniquename, t->tht_channel->ch_name);
+ t->tht_identifier, t->tht_channel->ch_name);
ct->ct_keystate = CT_RESOLVED;
set_control_words(ct->ct_keys, msg + 3, msg + 3 + 8);
#define DVB_FEC_ERROR_LIMIT 20
-extern struct th_dvb_adapter_list dvb_adapters_probing;
-extern struct th_dvb_adapter_list dvb_adapters_running;
-extern struct th_dvb_mux_list dvb_muxes;
+extern struct th_dvb_adapter_list dvb_adapters;
+extern struct th_dvb_mux_instance_list dvb_muxes;
void dvb_init(void);
void tdmi_check_scan_status(th_dvb_mux_instance_t *tdmi);
-th_transport_t *dvb_find_transport(th_dvb_mux_instance_t *tdmi, uint16_t tid,
+th_transport_t *dvb_find_transport(th_dvb_mux_instance_t *tdmi,
uint16_t sid, int pmt_pid);
+th_dvb_mux_instance_t *dvb_mux_create(th_dvb_adapter_t *tda,
+ struct dvb_frontend_parameters *fe_param,
+ int polarisation, int switchport,
+ int save);
+
+void dvb_tdmi_save(th_dvb_mux_instance_t *tdmi);
+
#endif /* DVB_H_ */
th_transport_t *t;
while(r >= 188) {
- LIST_FOREACH(t, &tda->tda_transports, tht_adapter_link)
+ LIST_FOREACH(t, &tda->tda_transports, tht_active_link)
ts_recv_packet1(t, tsb);
r -= 188;
tsb += 188;
{
th_stream_t *st;
- LIST_REMOVE(t, tht_adapter_link);
+ LIST_REMOVE(t, tht_active_link);
LIST_FOREACH(st, &t->tht_streams, st_link) {
close(st->st_demuxer_fd);
st->st_demuxer_fd = -1;
st->st_demuxer_fd = fd;
}
- LIST_INSERT_HEAD(&tda->tda_transports, t, tht_adapter_link);
+ LIST_INSERT_HEAD(&tda->tda_transports, t, tht_active_link);
t->tht_status = status;
dvb_tune_tdmi(tdmi, 1, TDMI_RUNNING);
p = *tdmi->tdmi_fe_params;
- if(tdmi->tdmi_type == FE_QPSK) {
+ if(tda->tda_fe_info->type == FE_QPSK) {
/* DVB-S */
int lowfreq, hifreq, switchfreq, hiband;
else
p.frequency = abs(p.frequency - lowfreq);
}
-
+
i = ioctl(tda->tda_fe_fd, FE_SET_FRONTEND, &p);
-
if(i != 0) {
syslog(LOG_ERR, "\"%s\" tuning to %dHz"
" -- Front configuration failed -- %s",
tdmi->tdmi_status = "No signal";
ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &v);
+ if(v < 0)
+ v = 0;
if(fe_status & FE_HAS_LOCK) {
tdmi->tdmi_fec_err_histogram[tdmi->tdmi_fec_err_ptr] = v;
if(maylog)
syslog(LOG_DEBUG, "\"%s\" tuning to mux \"%s\"",
- tda->tda_rootpath, tdmi->tdmi_shortname);
+ tda->tda_rootpath, "FIXME");
/* Add tables which will be activated once the tuning is completed */
#include "dvb.h"
#include "dvb_dvr.h"
#include "dvb_muxconfig.h"
+#include "dvb_support.h"
#include "strtab.h"
#include "transports.h"
-
-
-static void
-dvb_add_mux(struct dvb_frontend_parameters *fe_param, fe_type_t type,
- int polarisation)
-{
- th_dvb_adapter_t *tda;
- char buf[100];
- char *typetxt;
-
- th_dvb_mux_instance_t *tdmi;
-
- switch(type) {
- case FE_QPSK:
- typetxt = "DVB-S";
- break;
- case FE_QAM:
- typetxt = "DVB-C";
- break;
- case FE_OFDM:
- typetxt = "DVB-T";
- break;
- case FE_ATSC:
- typetxt = "ATSC";
- break;
- default:
- return;
- }
-
- LIST_FOREACH(tda, &dvb_adapters_probing, tda_link) {
- if(tda->tda_fe_info->type != type)
- continue; /* Does not match frontend */
-
- tdmi = calloc(1, sizeof(th_dvb_mux_instance_t));
- pthread_mutex_init(&tdmi->tdmi_table_lock, NULL);
-
- tdmi->tdmi_status = TDMI_CONFIGURED;
- tdmi->tdmi_adapter = tda;
- LIST_INSERT_HEAD(&tda->tda_muxes_configured, tdmi, tdmi_adapter_link);
-
- tdmi->tdmi_type = type;
-
- tdmi->tdmi_fe_params = malloc(sizeof(struct dvb_frontend_parameters));
- tdmi->tdmi_polarisation = polarisation;
-
- memcpy(tdmi->tdmi_fe_params, fe_param,
- sizeof(struct dvb_frontend_parameters));
-
- /* Generate names */
-
- if(tdmi->tdmi_type == FE_QPSK) {
- const char *pol;
-
- switch(polarisation) {
- case POLARISATION_VERTICAL: pol = "V"; break;
- case POLARISATION_HORIZONTAL: pol = "H"; break;
- default: pol = "X"; break;
- }
-
-
- /* Frequency is in kHz */
- snprintf(buf, sizeof(buf), "%s/%.1fMHz/%s",
- typetxt, (float)fe_param->frequency / 1000.0f, pol);
- } else {
- snprintf(buf, sizeof(buf), "%s/%.1fMHz",
- typetxt, (float)fe_param->frequency / 1000000.0f);
- }
- tdmi->tdmi_shortname = strdup(buf);
-
- snprintf(buf, sizeof(buf), "%s/%s", tda->tda_sname, tdmi->tdmi_shortname),
- tdmi->tdmi_uniquename = strdup(buf);
-
- }
-}
-
-
-
static struct strtab fectab[] = {
{ "NONE", FEC_NONE },
{ "1/2", FEC_1_2 },
{ "AUTO", TRANSMISSION_MODE_AUTO }
};
-
static struct strtab guardtab[] = {
{ "1/32", GUARD_INTERVAL_1_32 },
{ "1/16", GUARD_INTERVAL_1_16 },
{ "AUTO", HIERARCHY_AUTO }
};
+static struct strtab poltab[] = {
+ { "V", POLARISATION_VERTICAL },
+ { "H", POLARISATION_HORIZONTAL },
+};
+
+
+void
+dvb_mux_store(FILE *fp, th_dvb_mux_instance_t *tdmi)
+{
+ struct dvb_frontend_parameters *f = tdmi->tdmi_fe_params;
+
+ fprintf(fp, "\tfrequency = %d\n", f->frequency);
+
+ switch(tdmi->tdmi_adapter->tda_fe_info->type) {
+ case FE_OFDM:
+ fprintf(fp, "\tbandwidth = %s\n",
+ val2str(f->u.ofdm.bandwidth, bwtab));
+
+ fprintf(fp, "\tconstellation = %s\n",
+ val2str(f->u.ofdm.constellation, qamtab));
+
+ fprintf(fp, "\ttransmission_mode = %s\n",
+ val2str(f->u.ofdm.transmission_mode, modetab));
+
+ fprintf(fp, "\tguard_interval = %s\n",
+ val2str(f->u.ofdm.guard_interval, guardtab));
+
+ fprintf(fp, "\thierarchy = %s\n",
+ val2str(f->u.ofdm.hierarchy_information, hiertab));
+
+ fprintf(fp, "\tfec_hi = %s\n",
+ val2str(f->u.ofdm.code_rate_HP, fectab));
+
+ fprintf(fp, "\tfec_lo = %s\n",
+ val2str(f->u.ofdm.code_rate_LP, fectab));
+ break;
+
+ case FE_QPSK:
+ fprintf(fp, "\tsymbol_rate = %d\n", f->u.qpsk.symbol_rate);
+
+ fprintf(fp, "\tfec = %s\n",
+ val2str(f->u.qpsk.fec_inner, fectab));
+
+ fprintf(fp, "\tpolarisation = %s\n",
+ val2str(tdmi->tdmi_polarisation, poltab));
+
+ fprintf(fp, "\tswitchport = %d\n", tdmi->tdmi_switchport);
+ break;
+
+ case FE_QAM:
+ fprintf(fp, "\tsymbol_rate = %d\n", f->u.qam.symbol_rate);
+
+ fprintf(fp, "\tfec = %s\n",
+ val2str(f->u.qam.fec_inner, fectab));
+
+ fprintf(fp, "\tconstellation = %s\n",
+ val2str(f->u.qam.modulation, qamtab));
+ break;
+
+ case FE_ATSC:
+ break;
+ }
+}
+
+/**
+ *
+ */
+const char *
+dvb_mux_create_str(th_dvb_adapter_t *tda,
+ const char *freqstr,
+ const char *symratestr,
+ const char *qamstr,
+ const char *fecstr,
+ const char *fechistr,
+ const char *feclostr,
+ const char *bwstr,
+ const char *tmodestr,
+ const char *guardstr,
+ const char *hierstr,
+ const char *polstr,
+ const char *switchportstr,
+ int save)
+{
+ struct dvb_frontend_parameters f;
+ int r;
+ int polarisation = 0, switchport = 0;
+
+ memset(&f, 0, sizeof(f));
+
+ f.inversion = INVERSION_AUTO;
+
+ f.frequency = freqstr ? atoi(freqstr) : 0;
+ if(f.frequency == 0)
+ return "Invalid frequency";
+
+ switch(tda->tda_fe_info->type) {
+ case FE_OFDM:
+ if(bwstr == NULL || (r = str2val(bwstr, bwtab)) < 0)
+ return "Invalid bandwidth";
+ f.u.ofdm.bandwidth = r;
+
+ if(qamstr == NULL || (r = str2val(qamstr, qamtab)) < 0)
+ return "Invalid QAM constellation";
+ f.u.ofdm.constellation = r;
+
+ if(tmodestr == NULL || (r = str2val(tmodestr, modetab)) < 0)
+ return "Invalid transmission mode";
+ f.u.ofdm.transmission_mode = r;
+
+ if(guardstr == NULL || (r = str2val(guardstr, guardtab)) < 0)
+ return "Invalid guard interval";
+ f.u.ofdm.guard_interval = r;
+
+ if(hierstr == NULL || (r = str2val(hierstr, hiertab)) < 0)
+ return "Invalid heirarchy information";
+ f.u.ofdm.hierarchy_information = r;
+
+ if(fechistr == NULL || (r = str2val(fechistr, fectab)) < 0)
+ printf("hifec = %s\n", fechistr);
+ f.u.ofdm.code_rate_HP = r;
+
+ if(feclostr == NULL || (r = str2val(feclostr, fectab)) < 0)
+ return "Invalid lo-FEC";
+ f.u.ofdm.code_rate_LP = r;
+ break;
+
+ case FE_QPSK:
+ f.u.qpsk.symbol_rate = symratestr ? atoi(symratestr) : 0;
+ if(f.u.qpsk.symbol_rate == 0)
+ return "Invalid symbol rate";
+
+ if(fecstr == NULL || (r = str2val(fecstr, fectab)) < 0)
+ return "Invalid FEC";
+ f.u.qpsk.fec_inner = r;
+
+ if(polstr == NULL || (r = str2val(polstr, poltab)) < 0)
+ return "Invalid polarisation";
+ polarisation = r;
+
+ switchport = atoi(switchportstr);
+ break;
+
+ case FE_QAM:
+ f.u.qam.symbol_rate = symratestr ? atoi(symratestr) : 0;
+ if(f.u.qam.symbol_rate == 0)
+ return "Invalid symbol rate";
+
+ if(qamstr == NULL || (r = str2val(qamstr, qamtab)) < 0)
+ return "Invalid QAM constellation";
+ f.u.qam.modulation = r;
+
+ if(fecstr == NULL || (r = str2val(fecstr, fectab)) < 0)
+ return "Invalid FEC";
+ f.u.qam.fec_inner = r;
+ break;
+
+ case FE_ATSC:
+ break;
+ }
+
+ dvb_mux_create(tda, &f, polarisation, switchport, save);
+
+ return NULL;
+}
+
+
+#if 0
+
static void
dvb_muxfile_add(ce->ce_value);
}
+#endif
#ifndef DVB_MUXCONFIG_H_
#define DVB_MUXCONFIG_H_
-void dvb_mux_setup(void);
+void dvb_mux_store(FILE *fp, th_dvb_mux_instance_t *tdmi);
+
+const char *dvb_mux_create_str(th_dvb_adapter_t *tda,
+ const char *freqstr,
+ const char *symratestr,
+ const char *qamstr,
+ const char *fecstr,
+ const char *fechistr,
+ const char *feclostr,
+ const char *bwstr,
+ const char *tmodestr,
+ const char *guardstr,
+ const char *hierstr,
+ const char *polstr,
+ const char *switchportstr,
+ int save);
#endif /* DVB_MUXCONFIG_H */
#include <stdlib.h>
#include <string.h>
+#include <linux/dvb/frontend.h>
+
#include "tvhead.h"
#include "dvb_support.h"
+#include "strtab.h"
+#include "dvb.h"
/*
* DVB String conversion according to EN 300 468, Annex A
dvb_time.tm_yday = 0;
return (timegm(&dvb_time));
}
+
+/**
+ *
+ */
+static struct strtab adaptertype[] = {
+ { "DVB-S", FE_QPSK },
+ { "DVB-C", FE_QAM },
+ { "DVB-T", FE_OFDM },
+ { "ATSC", FE_ATSC },
+};
+
+const char *
+dvb_adaptertype_to_str(int type)
+{
+ return val2str(type, adaptertype) ?: "invalid";
+}
+
+const char *
+dvb_polarisation_to_str(int pol)
+{
+ switch(pol) {
+ case POLARISATION_VERTICAL: return "V";
+ case POLARISATION_HORIZONTAL: return "H";
+ default: return "X";
+ }
+}
+
+
+
+th_dvb_mux_instance_t *
+dvb_mux_find_by_identifier(const char *identifier)
+{
+ th_dvb_mux_instance_t *tdmi;
+
+ LIST_FOREACH(tdmi, &dvb_muxes, tdmi_global_link)
+ if(!strcmp(identifier, tdmi->tdmi_identifier))
+ return tdmi;
+ return NULL;
+}
+
+
+
+th_dvb_adapter_t *
+dvb_adapter_find_by_identifier(const char *identifier)
+{
+ th_dvb_adapter_t *tda;
+
+ LIST_FOREACH(tda, &dvb_adapters, tda_global_link)
+ if(!strcmp(identifier, tda->tda_identifier))
+ return tda;
+ return NULL;
+}
+
+
+
+
+/**
+ *
+ */
+const char *
+dvb_mux_status(th_dvb_mux_instance_t *tdmi)
+{
+ int i, v, vv;
+ const char *txt = tdmi->tdmi_status ?: "Ok";
+
+ v = vv = 0;
+ for(i = 0; i < TDMI_FEC_ERR_HISTOGRAM_SIZE; i++) {
+ if(tdmi->tdmi_fec_err_histogram[i] > DVB_FEC_ERROR_LIMIT)
+ v++;
+ vv += tdmi->tdmi_fec_err_histogram[i];
+ }
+ vv /= TDMI_FEC_ERR_HISTOGRAM_SIZE;
+
+ if(v == TDMI_FEC_ERR_HISTOGRAM_SIZE)
+ txt = "Constant high FEC rate";
+ else if(v > 0)
+ txt = "Bursty FEC rate";
+
+ return txt;
+}
#define DVB_DESC_NETWORK_NAME 0x40
#define DVB_DESC_SERVICE_LIST 0x41
+#define DVB_DESC_CABLE 0x44
#define DVB_DESC_SHORT_EVENT 0x4d
#define DVB_DESC_SERVICE 0x48
#define DVB_DESC_CONTENT 0x54
#define DVB_DESC_SUBTITLE 0x59
#define DVB_DESC_AC3 0x6a
-
-/* Service types defined in EN 300 468 */
-
-#define DVB_ST_SDTV 0x1 /* SDTV (MPEG2) */
-#define DVB_ST_RADIO 0x2
-#define DVB_ST_HDTV 0x11 /* HDTV (MPEG2) */
-#define DVB_ST_AC_SDTV 0x16 /* Advanced codec SDTV */
-#define DVB_ST_AC_HDTV 0x19 /* Advanced codec HDTV */
-
int dvb_get_string(char *dst, size_t dstlen, const uint8_t *src,
const size_t srclen, const char *target_encoding);
time_t dvb_convert_date(uint8_t *dvb_buf);
+const char *dvb_adaptertype_to_str(int type);
+const char *dvb_polarisation_to_str(int pol);
+th_dvb_adapter_t *dvb_adapter_find_by_identifier(const char *identifier);
+th_dvb_mux_instance_t *dvb_mux_find_by_identifier(const char *identifier);
+const char *dvb_mux_status(th_dvb_mux_instance_t *tdmi);
+
#endif /* DVB_SUPPORT_H */
ptr = &sec[3];
len -= 4; /* Strip trailing CRC */
- if(!tdt->tdt_callback(tdt->tdt_tdmi, ptr, len, tableid, tdt->tdt_opaque))
- tdt->tdt_count++;
-
- tdmi_check_scan_status(tdt->tdt_tdmi);
+ tdt->tdt_callback(tdt->tdt_tdmi, ptr, len, tableid, tdt->tdt_opaque);
}
*/
static void
tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams,
- int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
- uint8_t tableid, void *opaque), void *opaque,
- int initial_count, char *name, int flags)
+ void (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
+ uint8_t tableid, void *opaque), void *opaque,
+ char *name, int flags)
{
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
th_dvb_table_t *tdt;
tdt->tdt_opaque = opaque;
tdt->tdt_tdmi = tdmi;
tdt->tdt_handle = dispatch_addfd(fd, dvb_table_recv, tdt, DISPATCH_READ);
- tdt->tdt_count = initial_count;
if(flags & TDT_NOW) {
ioctl(fd, DMX_SET_FILTER, fparams);
/**
* DVB EIT (Event Information Table)
*/
-static int
+static void
dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
char desc[5000];
epg_content_type_t *ect;
- if(tableid < 0x4e || tableid > 0x6f)
- return -1;
-
- if(len < 11)
- return -1;
+ if(tableid < 0x4e || tableid > 0x6f || len < 11)
+ return;
serviceid = ptr[0] << 8 | ptr[1];
version = ptr[2] >> 1 & 0x1f;
len -= 11;
ptr += 11;
- t = dvb_find_transport(tdmi, transport_stream_id, serviceid, 0);
+ t = dvb_find_transport(tdmi, serviceid, 0);
if(t == NULL)
- return -1;
+ return;
+
ch = t->tht_channel;
if(ch == NULL)
- return -1;
+ return;
epg_lock();
}
epg_unlock();
- return 0;
}
/**
* DVB SDT (Service Description Table)
*/
-static int
+static void
dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
char provider[256];
char chname0[256], *chname;
uint8_t stype;
- int ret = 0, l;
-
- if(tdmi->tdmi_network == NULL)
- return -1;
+ int l;
+ int change = 0;
if(len < 8)
- return -1;
+ return;
transport_stream_id = ptr[0] << 8 | ptr[1];
version = ptr[2] >> 1 & 0x1f;
case DVB_DESC_SERVICE:
if(dvb_desc_service(ptr, dlen, &stype,
provider, sizeof(provider),
- chname0, sizeof(chname0)) < 0) {
- stype = 0;
- } else {
+ chname0, sizeof(chname0)) == 0) {
chname = chname0;
- /* Some providers insert spaces */
+ /* Some providers insert spaces.
+ Clean up that (both heading and trailing) */
while(*chname <= 32 && *chname != 0)
chname++;
l--;
}
- }
- break;
- }
+ t = dvb_find_transport(tdmi, service_id, 0);
+ if(t == NULL)
+ break;
- len -= dlen; ptr += dlen; dllen -= dlen;
- }
+ change |=
+ t->tht_servicetype != stype ||
+ t->tht_scrambled != free_ca_mode ||
+ strcmp(t->tht_provider ?: "", provider) ||
+ strcmp(t->tht_servicename ?: "", chname );
-
- if(chname != NULL) switch(stype) {
+ t->tht_servicetype = stype;
+ t->tht_scrambled = free_ca_mode;
- case DVB_ST_SDTV:
- case DVB_ST_HDTV:
- case DVB_ST_AC_SDTV:
- case DVB_ST_AC_HDTV:
- /* TV service */
+ free((void *)t->tht_provider);
+ t->tht_provider = strdup(provider);
- t = dvb_find_transport(tdmi, transport_stream_id, service_id, 0);
-
- if(t == NULL) {
- ret |= 1;
- } else {
- t->tht_scrambled = free_ca_mode;
-
- free((void *)t->tht_provider);
- t->tht_provider = strdup(provider);
-
- free((void *)t->tht_network);
- t->tht_network = strdup(tdmi->tdmi_network);
-
- if(t->tht_channel == NULL) {
- /* Not yet mapped to a channel */
- if(LIST_FIRST(&t->tht_streams) != NULL) {
- /* We have streams, map it */
- if(t->tht_scrambled)
- t->tht_prio = 75;
- else
- t->tht_prio = 25;
-
- transport_set_channel(t, channel_find(chname, 1, NULL));
- } else {
- if(t->tht_pmt_seen == 0)
- ret |= 1; /* Return error (so scanning wont continue yet) */
- }
+ free((void *)t->tht_servicename);
+ t->tht_servicename = strdup(chname);
+
+ if(t->tht_channelname == NULL)
+ t->tht_channelname = strdup(chname);
}
+ break;
}
- break;
+ len -= dlen; ptr += dlen; dllen -= dlen;
}
}
- return ret;
+ if(change)
+ dvb_tdmi_save(tdmi);
}
/**
* PAT - Program Allocation table
*/
-static int
+static void
dvb_pat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
uint16_t service, pmt, tid;
+ th_transport_t *t;
if(len < 5)
- return -1;
+ return;
tid = (ptr[0] << 8) | ptr[1];
while(len >= 4) {
service = ptr[0] << 8 | ptr[1];
pmt = (ptr[2] & 0x1f) << 8 | ptr[3];
-
- if(service != 0)
- dvb_find_transport(tdmi, tid, service, pmt);
+ if(service != 0) {
+ t = dvb_find_transport(tdmi, service, pmt);
+ dvb_table_add_transport(tdmi, t, pmt); /* Add PMT to our table scanner */
+ }
ptr += 4;
len -= 4;
}
- return 0;
}
/**
* CAT - Condition Access Table
*/
-static int
+static void
dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
ptr += tlen;
len -= tlen;
}
- return 0;
}
+/**
+ * Tables for delivery descriptor parsing
+ */
+static const fe_code_rate_t fec_tab [8] = {
+ FEC_AUTO, FEC_1_2, FEC_2_3, FEC_3_4,
+ FEC_5_6, FEC_7_8, FEC_NONE, FEC_NONE
+};
+
+
+static const fe_modulation_t qam_tab [6] = {
+ QAM_AUTO, QAM_16, QAM_32, QAM_64, QAM_128, QAM_256
+};
+
+/**
+ * Cable delivery descriptor
+ */
+static void
+dvb_table_cable_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len)
+{
+ int freq, symrate;
+ struct dvb_frontend_parameters fe_param;
+
+ if(len < 11) {
+ printf("Invalid CABLE DESCRIPTOR\n");
+ return;
+ }
+ memset(&fe_param, 0, sizeof(fe_param));
+ fe_param.inversion = INVERSION_AUTO;
+
+ freq =
+ bcdtoint(ptr[0]) * 1000000 + bcdtoint(ptr[1]) * 10000 +
+ bcdtoint(ptr[2]) * 100 + bcdtoint(ptr[3]);
+
+ fe_param.frequency = freq * 100;
+
+ symrate =
+ bcdtoint(ptr[7]) * 100000 + bcdtoint(ptr[8]) * 1000 +
+ bcdtoint(ptr[9]) * 10 + (ptr[10] >> 4);
+
+ fe_param.u.qam.symbol_rate = symrate * 100;
+
+
+ if((ptr[6] & 0x0f) > 5)
+ fe_param.u.qam.modulation = QAM_AUTO;
+ else
+ fe_param.u.qam.modulation = qam_tab[ptr[6] & 0x0f];
+
+ fe_param.u.qam.fec_inner = fec_tab[ptr[10] & 0x07];
+
+ dvb_mux_create(tdmi->tdmi_adapter, &fe_param, 0, 0, 1);
+}
+
+
+
/**
* NIT - Network Information Table
*/
-static int
+static void
dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
uint8_t tag, tlen;
int ntl;
char networkname[256];
+ uint16_t tsid;
ptr += 5;
len -= 5;
if(tableid != 0x40)
- return -1;
+ return;
ntl = ((ptr[0] & 0xf) << 8) | ptr[1];
ptr += 2;
len -= 2;
if(ntl > len)
- return 0;
+ return;
while(ntl > 2) {
tag = *ptr++;
switch(tag) {
case DVB_DESC_NETWORK_NAME:
if(dvb_get_string(networkname, sizeof(networkname), ptr, tlen, "UTF8"))
- return 0;
+ return;
free((void *)tdmi->tdmi_network);
tdmi->tdmi_network = strdup(networkname);
ntl -= tlen;
}
- return 0;
+ if(len < 2)
+ return;
+
+ ntl = ((ptr[0] & 0xf) << 8) | ptr[1];
+ ptr += 2;
+ len -= 2;
+
+ if(len < ntl)
+ return;
+
+ while(len >= 6) {
+ tsid = ( ptr[0] << 8) | ptr[1];
+ ntl = ((ptr[4] & 0xf) << 8) | ptr[5];
+
+ ptr += 6;
+ len -= 6;
+ if(ntl > len)
+ break;
+
+ while(ntl > 2) {
+ tag = *ptr++;
+ tlen = *ptr++;
+ len -= 2;
+ ntl -= 2;
+
+ switch(tag) {
+ case DVB_DESC_CABLE:
+ dvb_table_cable_delivery(tdmi, ptr, tlen);
+ break;
+ }
+
+ ptr += tlen;
+ len -= tlen;
+ ntl -= tlen;
+ }
+ }
}
/**
* PMT - Program Mapping Table
*/
-static int
+static void
dvb_pmt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
th_transport_t *t = opaque;
+ int v = t->tht_pmt_seen;
- return psi_parse_pmt(t, ptr, len, 1);
+ psi_parse_pmt(t, ptr, len, 1);
+ v ^= t->tht_pmt_seen;
+ if(v)
+ dvb_tdmi_save(tdmi);
+ return;
}
/**
* RST - Running Status Table
*/
-static int
+static void
dvb_rst_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
uint8_t tableid, void *opaque)
{
int i;
- printf("Got RST on %s\t", tdmi->tdmi_uniquename);
+ // printf("Got RST on %s\t", tdmi->tdmi_uniquename);
for(i = 0; i < len; i++)
printf("%02x.", ptr[i]);
printf("\n");
- return 0;
}
fp = dvb_fparams_alloc(0x0, DMX_IMMEDIATE_START | DMX_CHECK_CRC);
fp->filter.filter[0] = 0x00;
fp->filter.mask[0] = 0xff;
- tdt_add(tdmi, fp, dvb_pat_callback, NULL, 0, "pat", 0);
+ tdt_add(tdmi, fp, dvb_pat_callback, NULL, "pat", 0);
/* Conditional Access Table */
fp = dvb_fparams_alloc(0x1, DMX_IMMEDIATE_START | DMX_CHECK_CRC);
fp->filter.filter[0] = 0x1;
fp->filter.mask[0] = 0xff;
- tdt_add(tdmi, fp, dvb_cat_callback, NULL, 1, "cat", 0);
+ tdt_add(tdmi, fp, dvb_cat_callback, NULL, "cat", 0);
/* Network Information Table */
fp = dvb_fparams_alloc(0x10, DMX_IMMEDIATE_START | DMX_CHECK_CRC);
- tdt_add(tdmi, fp, dvb_nit_callback, NULL, 0, "nit", 0);
+ tdt_add(tdmi, fp, dvb_nit_callback, NULL, "nit", 0);
/* Service Descriptor Table */
fp = dvb_fparams_alloc(0x11, DMX_IMMEDIATE_START | DMX_CHECK_CRC);
fp->filter.filter[0] = 0x42;
fp->filter.mask[0] = 0xff;
- tdt_add(tdmi, fp, dvb_sdt_callback, NULL, 0, "sdt", 0);
+ tdt_add(tdmi, fp, dvb_sdt_callback, NULL, "sdt", 0);
/* Event Information table */
fp = dvb_fparams_alloc(0x12, DMX_IMMEDIATE_START | DMX_CHECK_CRC);
- tdt_add(tdmi, fp, dvb_eit_callback, NULL, 1, "eit", 0);
+ tdt_add(tdmi, fp, dvb_eit_callback, NULL, "eit", 0);
/* Running Status Table */
fp = dvb_fparams_alloc(0x13, DMX_IMMEDIATE_START | DMX_CHECK_CRC);
fp->filter.filter[0] = 0x71;
fp->filter.mask[0] = 0xff;
- tdt_add(tdmi, fp, dvb_rst_callback, NULL, 1, "rst", 0);
+ tdt_add(tdmi, fp, dvb_rst_callback, NULL, "rst", 0);
}
fp = dvb_fparams_alloc(pmt_pid, DMX_IMMEDIATE_START | DMX_CHECK_CRC);
fp->filter.filter[0] = 0x02;
fp->filter.mask[0] = 0xff;
- tdt_add(tdmi, fp, dvb_pmt_callback, t, 0, pmtname, TDT_NOW);
+ tdt_add(tdmi, fp, dvb_pmt_callback, t, pmtname, TDT_NOW);
}
fi->fi_name = strdup(s);
t = calloc(1, sizeof(th_transport_t));
- t->tht_prio = 100;
t->tht_type = TRANSPORT_STREAMEDFILE;
t->tht_start_feed = file_input_start_feed;
t->tht_name = strdup(ch->ch_name);
t->tht_provider = strdup("HTS Tvheadend");
t->tht_network = strdup("Internal");
- t->tht_uniquename = strdup(ch->ch_name);
+ t->tht_identifier = strdup(ch->ch_name);
t->tht_file_input = fi;
- transport_link(t, ch, THT_OTHER);
+ transport_set_channel(t, ch->ch_name);
}
}
}
}
-/*
- *
- */
-static void
-print_tdmi(client_t *c, th_dvb_mux_instance_t *tdmi)
-{
- switch(tdmi->tdmi_state) {
- case TDMI_CONFIGURED:
- cprintf(c, "Configured, awaiting scan slot\n");
- return;
-
- case TDMI_INITIAL_SCAN:
- cprintf(c, "Initial scan\n");
- return;
-
- case TDMI_IDLE:
- cprintf(c, " Idle since %s", ctime(&tdmi->tdmi_lost_adapter));
- cprintf(c, "\tLast known status: ");
- break;
-
- case TDMI_RUNNING:
- cprintf(c, "Running since %s", ctime(&tdmi->tdmi_got_adapter));
- cprintf(c, "\t Current status: ");
- break;
-
- case TDMI_IDLESCAN:
- cprintf(c, "Idle but scanning\n");
- cprintf(c, "\t Current status: ");
- break;
- }
-
- if(tdmi->tdmi_status != NULL) {
- cprintf(c, "%s\n", tdmi->tdmi_status);
- return;
- }
-
- cprintf(c, "locked\n");
-}
-
-/*
- *
- */
-static int
-cr_show(client_t *c, char **argv, int argc)
-{
- const char *subcmd;
- th_subscription_t *s;
- th_transport_t *t;
- th_channel_t *ch;
- th_dvb_adapter_t *tda;
- th_dvb_mux_instance_t *tdmi;
- event_t *e;
- char *tmp;
- char *txt;
- int v, remain;
-
- if(argc != 1)
- return 1;
-
- subcmd = argv[0];
-
- if(!strcasecmp(subcmd, "subscriptions")) {
- cprintf(c, "prio %-18s %-20s %s\n", "output", "channel", "source");
- cprintf(c, "-----------------------------------------------------------------------------\n");
-
- LIST_FOREACH(s, &subscriptions, ths_global_link) {
-
- cprintf(c, "%4d %-18s %-20s %s\n",
- s->ths_weight,
- s->ths_title, s->ths_channel->ch_name,
- s->ths_transport ? s->ths_transport->tht_name : "no transport");
- }
- return 0;
- }
-
- if(!strcasecmp(subcmd, "channel")) {
-
- LIST_FOREACH(ch, &channels, ch_global_link) {
-
- tmp = utf8toprintable(ch->ch_name);
- cprintf(c, "%3d: \"%s\"\n", ch->ch_index, tmp);
- free(tmp);
-
- epg_lock();
-
- e = epg_event_get_current(ch);
-
- if(e != NULL) {
-
- tmp = utf8toprintable(e->e_title ?: "<no current event>");
-
- remain = e->e_start + e->e_duration - time(NULL);
- remain /= 60;
-
- switch(e->e_source) {
- case EVENT_SRC_XMLTV:
- txt = "xmltv";
- break;
- case EVENT_SRC_DVB:
- txt = "dvb";
- break;
- default:
- txt = "???";
- break;
- }
-
- cprintf(c, "\tNow: %-40s %2d:%02d [%s] tag: %d\n",
- tmp, remain / 60, remain % 60, txt, e->e_tag);
- free(tmp);
- }
-
- epg_unlock();
-
- LIST_FOREACH(t, &ch->ch_transports, tht_channel_link) {
-
- cprintf(c, "\t%-47s", t->tht_name);
-
- switch(t->tht_status) {
- case TRANSPORT_IDLE:
- cprintf(c, "idle\n");
- break;
-
- case TRANSPORT_RUNNING:
- v = avgstat_read_and_expire(&t->tht_rate, dispatch_clock);
- cprintf(c, "running (%d kb/s)\n",
- v * 8 / 1000 / 10);
- break;
-
- default:
- continue;
- }
-
- v = avgstat_read(&t->tht_cc_errors, 60, dispatch_clock);
-
- if(v)
- cprintf(c, "\t\t%d error%s last minute %f / second\n",
- v, v == 1 ? "" : "s", v / 60.);
-
- v = avgstat_read_and_expire(&t->tht_cc_errors, dispatch_clock);
-
- if(v)
- cprintf(c, "\t\t%d error%s last hour %f / second\n",
- v, v == 1 ? "" : "s", v / 3600.);
-
- LIST_FOREACH(s, &t->tht_subscriptions, ths_transport_link) {
- cprintf(c, "\t\t%s @ prio %d, since %s",
- s->ths_title, s->ths_weight, ctime(&s->ths_start));
- if(s->ths_total_err) {
- cprintf(c,"\t\t\t%d error%s seen\n",
- s->ths_total_err, s->ths_total_err == 1 ? "" : "s");
- }
- }
- }
- cprintf(c, "\n");
-
-
- }
- return 0;
- }
-
- if(!strcasecmp(subcmd, "dvbadapters")) {
- LIST_FOREACH(tda, &dvb_adapters_running, tda_link) {
-
- cprintf(c, "%20s: ", tda->tda_rootpath);
-
- tdmi = tda->tda_mux_current;
-
- if(tdmi == NULL) {
- cprintf(c, "inactive\n");
- continue;
- }
-
- cprintf(c, "Tuned to \"%s\"\n", tdmi->tdmi_shortname);
- cprintf(c, "\t\t ");
-
- print_tdmi(c, tda->tda_mux_current);
- cprintf(c, "\n");
- }
- return 0;
- }
-
- if(!strcasecmp(subcmd, "storage")) {
- cprintf(c, "In-memory storage %lld / %lld\n",
- store_mem_size, store_mem_size_max);
-
- cprintf(c, " On-disk storage %lld / %lld\n",
- store_disk_size, store_disk_size_max);
-
- cprintf(c, " %d packets in memory\n",
- store_packets);
- return 0;
- }
-
- return 1;
-}
-
-
/*
*
*/
const char *name;
int (*func)(client_t *c, char *argv[], int argc);
} cr_cmds[] = {
- { "show", cr_show },
{ "streamport", cr_streamport },
{ "channels.list", cr_channels_list },
{ "channel.info", cr_channel_info },
int n, v;
char *argv[3], *c;
- // printf("HTTP INPUT: %s\n", buf);
+ //printf("HTTP INPUT: %s\n", buf);
switch(hc->hc_state) {
case HTTP_CON_WAIT_REQUEST:
ifname, inet_ntoa(t->tht_iptv_group_addr), t->tht_iptv_port);
t->tht_name = strdup(buf);
- st = transport_add_stream(t, 0, HTSTV_TABLE);
+ st = transport_add_stream(t, 0, HTSTV_PAT);
st->st_got_section = iptv_parse_pat;
st->st_section_docrc = 1;
- t->tht_prio = 50;
-
s = config_get_str_sub(head, "provider", NULL);
if(s != NULL)
t->tht_provider = strdup(s);
else
t->tht_network = strdup(inet_ntoa(t->tht_iptv_group_addr));
- snprintf(buf, sizeof(buf), "IPTV:%s:%d",
+ snprintf(buf, sizeof(buf), "iptv_%s_%d",
inet_ntoa(t->tht_iptv_group_addr), t->tht_iptv_port);
- t->tht_uniquename = strdup(buf);
+ t->tht_identifier = strdup(buf);
+
+ t->tht_channelname = strdup(channel_name);
- t->tht_channel = channel_find(channel_name, 1, NULL);
- LIST_INSERT_HEAD(&iptv_probing_transports, t, tht_adapter_link);
+ LIST_INSERT_HEAD(&iptv_probing_transports, t, tht_active_link);
startupcounter++;
if(!dtimer_isarmed(&iptv_probe_timer)) {
LIST_FOREACH(st, &t->tht_streams, st_link)
pidcnt++;
- LIST_REMOVE(t, tht_adapter_link);
+ LIST_REMOVE(t, tht_active_link);
syslog(LOG_INFO, "iptv: Transport %s probed, %d pids found%s",
t->tht_name, pidcnt, timeout ? ", but probe timeouted" : "");
iptv_stop_feed(t);
if(!timeout)
- transport_link(t, t->tht_channel, THT_MPEG_TS);
+ transport_set_channel(t, t->tht_channelname);
else
- LIST_INSERT_HEAD(&iptv_stale_transports, t, tht_adapter_link);
+ LIST_INSERT_HEAD(&iptv_stale_transports, t, tht_active_link);
t = LIST_FIRST(&iptv_probing_transports);
if(t == NULL)
settings_dir = config_get_str("settings-dir", NULL);
+ settings_dir = NULL;
+
if(settings_dir == NULL) {
settings_dir = "/tmp/tvheadend";
sys_warning =
snprintf(buf, sizeof(buf), "%s/autorec", settings_dir);
mkdir(buf, 0777);
+ snprintf(buf, sizeof(buf), "%s/dvbadapters", settings_dir);
+ mkdir(buf, 0777);
+
+ snprintf(buf, sizeof(buf), "%s/dvbadapters", settings_dir);
+ mkdir(buf, 0777);
+
+ snprintf(buf, sizeof(buf), "%s/dvbmuxes", settings_dir);
+ mkdir(buf, 0777);
+
syslog(LOG_NOTICE, "Started HTS TV Headend, settings located in \"%s\"",
settings_dir);
subscriptions_init();
- htmlui_start();
+ // htmlui_start();
if(doajax)
ajaxui_start();
}
-
-
-
-
-const char *
-htstvstreamtype2txt(tv_streamtype_t s)
-{
- switch(s) {
- case HTSTV_MPEG2VIDEO: return "mpeg2video";
- case HTSTV_MPEG2AUDIO: return "mpeg2audio";
- case HTSTV_H264: return "h264";
- case HTSTV_AC3: return "AC-3";
- case HTSTV_TELETEXT: return "teletext";
- case HTSTV_SUBTITLES: return "subtitles";
- case HTSTV_TABLE: return "PSI table";
- default: return "<unknown>";
- }
-}
-
-
FILE *
settings_open_for_write(const char *name)
{
#include <string.h>
#include <libavutil/common.h>
+#include <libavutil/avstring.h>
#include "tvhead.h"
#include "psi.h"
pid = (ptr[2] & 0x1f) << 8 | ptr[3];
if(prognum != 0) {
- st = transport_add_stream(t, pid, HTSTV_TABLE);
+ st = transport_add_stream(t, pid, HTSTV_PMT);
st->st_section_docrc = 1;
st->st_got_section = pmt_callback;
switch(dtag) {
case DVB_DESC_CA:
- st = transport_add_stream(t, (ptr[2] & 0x1f) << 8 | ptr[3], HTSTV_TABLE);
+ st = transport_add_stream(t, (ptr[2] & 0x1f) << 8 | ptr[3], HTSTV_CA);
st->st_caid = (ptr[0] << 8) | ptr[1];
break;
{
return val2str(caid, caidnametab);
}
+
+/**
+ *
+ */
+static struct strtab streamtypetab[] = {
+ { "MPEG2VIDEO", HTSTV_MPEG2VIDEO },
+ { "MPEG2AUDIO", HTSTV_MPEG2AUDIO },
+ { "H264", HTSTV_H264 },
+ { "AC3", HTSTV_AC3 },
+ { "TELETEXT", HTSTV_TELETEXT },
+ { "SUBTITLES", HTSTV_SUBTITLES },
+ { "CA", HTSTV_CA },
+ { "PMT", HTSTV_PMT },
+ { "PAT", HTSTV_PAT },
+};
+
+
+
+
+
+
+
+const char *
+htstvstreamtype2txt(tv_streamtype_t s)
+{
+ return val2str(s, streamtypetab) ?: "INVALID";
+}
+
+
+/**
+ * Save transport info
+ */
+void
+psi_save_transport(FILE *fp, th_transport_t *t)
+{
+ th_stream_t *st;
+
+ fprintf(fp, "\tpcr = %d\n", t->tht_pcr_pid);
+
+ LIST_FOREACH(st, &t->tht_streams, st_link) {
+ fprintf(fp, "\tstream {\n");
+ fprintf(fp, "\t\tpid = %d\n", st->st_pid);
+ fprintf(fp, "\t\ttype = %s\n", val2str(st->st_type, streamtypetab) ?: "?");
+
+ if(st->st_lang[0])
+ fprintf(fp, "\t\tlanguage = %s\n", st->st_lang);
+
+ if(st->st_type == HTSTV_CA)
+ fprintf(fp, "\t\tcaid = %s\n", val2str(st->st_caid, caidnametab) ?: "?");
+
+ if(st->st_frame_duration)
+ fprintf(fp, "\t\tframeduration = %d\n", st->st_frame_duration);
+
+ fprintf(fp, "\t}\n");
+ }
+}
+
+
+
+/**
+ * Load transport info
+ */
+void
+psi_load_transport(struct config_head *cl, th_transport_t *t)
+{
+ th_stream_t *st;
+ config_entry_t *ce;
+ tv_streamtype_t type;
+ const char *v;
+ int pid;
+
+ t->tht_pcr_pid = atoi(config_get_str_sub(cl, "pcr", "0"));
+
+ TAILQ_FOREACH(ce, cl, ce_link) {
+ if(ce->ce_type != CFG_SUB || strcasecmp("stream", ce->ce_key))
+ continue;
+
+ type = str2val(config_get_str_sub(&ce->ce_sub, "type", ""), streamtypetab);
+ if(type == -1)
+ continue;
+
+ pid = atoi(config_get_str_sub(&ce->ce_sub, "pid", "0"));
+ if(pid < 1)
+ continue;
+
+ st = transport_add_stream(t, pid, type);
+ st->st_tb = (AVRational){1, 90000};
+
+ v = config_get_str_sub(&ce->ce_sub, "lang", NULL);
+ if(v != NULL)
+ av_strlcpy(st->st_lang, v, 4);
+
+ st->st_frame_duration =
+ atoi(config_get_str_sub(&ce->ce_sub, "frameduration", "0"));
+
+ v = config_get_str_sub(&ce->ce_sub, "caid", NULL);
+ if(v != NULL)
+ st->st_caid = str2val(v, caidnametab);
+ }
+}
+
+
const char *psi_caid2name(uint16_t caid);
+void psi_save_transport(FILE *fp, th_transport_t *t);
+void psi_load_transport(struct config_head *cl, th_transport_t *t);
+
#endif /* PSI_H_ */
#include "tvhead.h"
#include "dispatch.h"
#include "dvb_dvr.h"
+#include "dvb_support.h"
#include "teletext.h"
#include "transports.h"
#include "subscriptions.h"
#include "buffer.h"
#include "channels.h"
#include "cwc.h"
+#include "strtab.h"
-static dtimer_t transport_monitor_timer;
+#define TRANSPORT_HASH_WIDTH 101
-static const char *transport_settings_path(th_transport_t *t);
+static struct th_transport_list transporthash[TRANSPORT_HASH_WIDTH];
+//static dtimer_t transport_monitor_timer;
+
+//static const char *transport_settings_path(th_transport_t *t);
+//static void transport_monitor(void *aux, int64_t now);
+
void
transport_stop(th_transport_t *t, int flush_subscriptions)
{
return;
}
+ // dtimer_disarm(&transport_monitor_timer, transport_monitor, t, 1);
+
t->tht_stop_feed(t);
while((td = LIST_FIRST(&t->tht_descramblers)) != NULL)
}
+/**
+ * Return prio for the given transport
+ */
+static int
+transport_get_prio(th_transport_t *t)
+{
+ switch(t->tht_type) {
+ case TRANSPORT_AVGEN:
+ case TRANSPORT_STREAMEDFILE:
+ return 0;
+
+ case TRANSPORT_DVB:
+ if(t->tht_scrambled)
+ return 3;
+ return 1;
+
+ case TRANSPORT_IPTV:
+ return 2;
+
+ case TRANSPORT_V4L:
+ return 4;
+
+ default:
+ return 5;
+ }
+}
+
+
+/**
+ * a - b -> lowest number first
+ */
+static int
+transportcmp(const void *A, const void *B)
+{
+ th_transport_t *a = *(th_transport_t **)A;
+ th_transport_t *b = *(th_transport_t **)B;
+
+ return transport_get_prio(a) - transport_get_prio(b);
+}
+/**
+ *
+ */
th_transport_t *
transport_find(th_channel_t *ch, unsigned int weight)
{
- th_transport_t *t;
+ th_transport_t *t, **vec;
+ int cnt = 0, i;
+
+ /* First, sort all transports in order */
+
+ LIST_FOREACH(t, &ch->ch_transports, tht_channel_link)
+ cnt++;
+
+ vec = alloca(cnt * sizeof(th_transport_t *));
+ i = 0;
+ LIST_FOREACH(t, &ch->ch_transports, tht_channel_link)
+ vec[i++] = t;
+
+ /* Sort transports, lower priority should come come earlier in the vector
+ (i.e. it will be more favoured when selecting a transport */
+
+ qsort(vec, cnt, sizeof(th_transport_t *), transportcmp);
/* First, try all transports without stealing */
th_subscription_t *s;
int w = 0;
- LIST_FOREACH(t, head, tht_adapter_link) {
+ LIST_FOREACH(t, head, tht_active_link) {
LIST_FOREACH(s, &t->tht_subscriptions, ths_transport_link) {
if(s->ths_weight > w)
w = s->ths_weight;
}
-
+#if 0
static void
}
}
+#endif
+
/**
- *
+ * Create and initialize a new transport struct
*/
-void
-transport_init(th_transport_t *t, int source_type)
+th_transport_t *
+transport_create(const char *identifier, int type, int source_type)
{
+ unsigned int hash = tvh_strhash(identifier, TRANSPORT_HASH_WIDTH);
+ th_transport_t *t = calloc(1, sizeof(th_transport_t));
+ t->tht_identifier = strdup(identifier);
+ t->tht_type = type;
t->tht_source_type = source_type;
- avgstat_init(&t->tht_cc_errors, 3600);
- avgstat_init(&t->tht_rate, 10);
-
- dtimer_arm(&transport_monitor_timer, transport_monitor, t, 5);
+ LIST_INSERT_HEAD(&transporthash[hash], t, tht_hash_link);
+ return t;
}
-
/**
- *
+ * Find a transport based on the given identifier
*/
-void
-transport_link(th_transport_t *t, th_channel_t *ch, int source_type)
+th_transport_t *
+transport_find_by_identifier(const char *identifier)
{
- transport_set_channel(t, ch);
- transport_init(t, source_type);
- LIST_INSERT_HEAD(&all_transports, t, tht_global_link);
+ th_transport_t *t;
+ unsigned int hash = tvh_strhash(identifier, TRANSPORT_HASH_WIDTH);
+ LIST_FOREACH(t, &transporthash[hash], tht_hash_link)
+ if(!strcmp(t->tht_identifier, identifier))
+ break;
+ return t;
}
+/**
+ * Add a new stream to a transport
+ */
th_stream_t *
transport_add_stream(th_transport_t *t, int pid, tv_streamtype_t type)
{
/**
*
*/
-static int
-transportcmp(th_transport_t *a, th_transport_t *b)
-{
- return a->tht_prio - b->tht_prio;
-}
-
-
-/**
- *
- */
-int
-transport_set_channel(th_transport_t *t, th_channel_t *ch)
+void
+transport_set_channel(th_transport_t *t, const char *chname)
{
- th_stream_t *st;
- char *chname;
- const char *n;
- char pid[30];
- char lang[30];
- struct config_head cl;
-
- assert(t->tht_uniquename != NULL);
-
- n = transport_settings_path(t);
- TAILQ_INIT(&cl);
- config_read_file0(n, &cl);
-
- t->tht_prio = atoi(config_get_str_sub(&cl, "prio", "0"));
- n = config_get_str_sub(&cl, "channel", NULL);
- if(n != NULL)
- ch = channel_find(n, 1, NULL);
+ th_channel_t *ch = channel_find(chname, 1, NULL);
- config_free0(&cl);
+ avgstat_init(&t->tht_cc_errors, 3600);
+ avgstat_init(&t->tht_rate, 10);
+ assert(t->tht_identifier != NULL);
t->tht_channel = ch;
- LIST_INSERT_SORTED(&ch->ch_transports, t, tht_channel_link, transportcmp);
-
- chname = utf8toprintable(ch->ch_name);
-
- syslog(LOG_DEBUG, "Added service \"%s\" for channel \"%s\"",
- t->tht_name, chname);
- free(chname);
-
- LIST_FOREACH(st, &t->tht_streams, st_link) {
- if(st->st_caid != 0) {
- n = psi_caid2name(st->st_caid);
- } else {
- n = htstvstreamtype2txt(st->st_type);
- }
- if(st->st_pid < 8192) {
- snprintf(pid, sizeof(pid), " on pid [%d]", st->st_pid);
- } else {
- pid[0] = 0;
- }
-
- if(st->st_lang[0]) {
- snprintf(lang, sizeof(lang), ", language \"%s\"", st->st_lang);
- } else {
- lang[0] = 0;
- }
-
- syslog(LOG_DEBUG, " Stream \"%s\"%s%s", n, lang, pid);
- }
- return 0;
+ LIST_INSERT_HEAD(&ch->ch_transports, t, tht_channel_link);
}
-
/**
*
*/
void
-transport_set_priority(th_transport_t *t, int prio)
+transport_unset_channel(th_transport_t *t)
{
- th_channel_t *ch = t->tht_channel;
-
- if(t->tht_prio == prio)
- return;
-
+ t->tht_channel = NULL;
LIST_REMOVE(t, tht_channel_link);
- t->tht_prio = prio;
- LIST_INSERT_SORTED(&ch->ch_transports, t, tht_channel_link, transportcmp);
- transport_settings_write(t);
}
+
/**
*
*/
-void
-transport_move(th_transport_t *t, th_channel_t *ch)
-{
- LIST_REMOVE(t, tht_channel_link);
- t->tht_channel = ch;
- LIST_INSERT_SORTED(&ch->ch_transports, t, tht_channel_link, transportcmp);
- transport_settings_write(t);
-}
+static struct strtab stypetab[] = {
+ { "SDTV", ST_SDTV },
+ { "Radio", ST_RADIO },
+ { "HDTV", ST_HDTV },
+ { "SDTV-AC", ST_AC_SDTV },
+ { "HDTV-AC", ST_AC_HDTV },
+};
-/**
- * Generate a settings-dir relative path to transport config
- *
- * Must be free'd by caller
- */
-static const char *
-transport_settings_path(th_transport_t *t)
+const char *
+transport_servicetype_txt(th_transport_t *t)
{
- char buf[400];
- char buf2[400];
-
- int l, i;
- char c;
-
- l = strlen(t->tht_uniquename);
- if(l >= sizeof(buf2))
- l = sizeof(buf2) - 1;
-
- for(i = 0; i < l; i++) {
- c = tolower(t->tht_uniquename[i]);
- if(isalnum(c))
- buf2[i] = c;
- else
- buf2[i] = '_';
- }
-
- buf2[i] = 0;
- snprintf(buf, sizeof(buf), "%s/transports/%s", settings_dir, buf2);
- return strdup(buf);
+ return val2str(t->tht_servicetype, stypetab);
}
-
/**
- * Write out a config file for a channel
+ *
*/
-void
-transport_settings_write(th_transport_t *t)
+int
+transport_is_tv(th_transport_t *t)
{
- FILE *fp;
- const char *n;
-
- n = transport_settings_path(t);
-
- if((fp = settings_open_for_write(n)) != NULL) {
- fprintf(fp,
- "uniquename = %s\n"
- "channel = %s\n"
- "prio = %d\n",
- t->tht_uniquename,
- t->tht_channel->ch_name,
- t->tht_prio);
- fclose(fp);
- }
- free((void *)n);
+ return
+ t->tht_servicetype == ST_SDTV ||
+ t->tht_servicetype == ST_HDTV ||
+ t->tht_servicetype == ST_AC_SDTV ||
+ t->tht_servicetype == ST_AC_HDTV;
}
-
void transport_stop(th_transport_t *t, int flush_subscriptions);
-void transport_init(th_transport_t *t, int source_type);
+th_transport_t *transport_create(const char *identifier, int type,
+ int source_type);
-void transport_link(th_transport_t *t, th_channel_t *ch, int source_type);
+th_transport_t *transport_find_by_identifier(const char *identifier);
-int transport_set_channel(th_transport_t *t, th_channel_t *ch);
+void transport_set_channel(th_transport_t *t, const char *name);
+
+void transport_unset_channel(th_transport_t *t);
th_transport_t *transport_find(th_channel_t *ch, unsigned int weight);
void transport_set_priority(th_transport_t *t, int prio);
-void transport_move(th_transport_t *t, th_channel_t *ch);
-
void transport_settings_write(th_transport_t *t);
+const char *transport_servicetype_txt(th_transport_t *t);
+
+int transport_is_tv(th_transport_t *t);
+
#endif /* TRANSPORTS_H */
{
th_descrambler_t *td;
- LIST_FOREACH(td, &t->tht_descramblers, td_transport_link)
- td->td_table(td, t, st,
- st->st_section->ps_data, st->st_section->ps_offset);
-
- if(st->st_got_section != NULL)
+ if(st->st_type == HTSTV_CA) {
+ LIST_FOREACH(td, &t->tht_descramblers, td_transport_link)
+ td->td_table(td, t, st,
+ st->st_section->ps_data, st->st_section->ps_offset);
+ } else if(st->st_got_section != NULL) {
st->st_got_section(t, st, st->st_section->ps_data,
st->st_section->ps_offset);
+ }
}
switch(st->st_type) {
- case HTSTV_TABLE:
+ case HTSTV_CA:
+ case HTSTV_PAT:
+ case HTSTV_PMT:
if(st->st_section == NULL)
st->st_section = calloc(1, sizeof(struct psi_section));
* Mux instance modes
*/
typedef enum {
- TDMI_CONFIGURED,
- TDMI_INITIAL_SCAN,
- TDMI_IDLE,
- TDMI_RUNNING,
- TDMI_IDLESCAN,
+ TDMI_IDLE, /* Not tuned */
+ TDMI_RUNNING, /* Tuned with a subscriber */
+ TDMI_IDLESCAN, /* Just scanning */
} tdmi_state_t;
/*
* DVB Mux instance
*/
typedef struct th_dvb_mux_instance {
+ LIST_ENTRY(th_dvb_mux_instance) tdmi_global_link;
LIST_ENTRY(th_dvb_mux_instance) tdmi_adapter_link;
struct th_dvb_adapter *tdmi_adapter;
time_t tdmi_got_adapter;
time_t tdmi_lost_adapter;
- int tdmi_type; /* really fe_type_t */
struct dvb_frontend_parameters *tdmi_fe_params;
- int tdmi_polarisation;
+ uint8_t tdmi_polarisation; /* for DVB-S */
+ uint8_t tdmi_switchport; /* for DVB-S */
- const char *tdmi_shortname; /* Only unique for the specific adapter */
- const char *tdmi_uniquename; /* Globally unique */
+ char *tdmi_identifier;
const char *tdmi_network; /* Name of network, from NIT table */
-} th_dvb_mux_instance_t;
+ struct th_transport_list tdmi_transports; /* via tht_mux_link */
+
+} th_dvb_mux_instance_t;
/*
char *tdt_name;
void *tdt_handle;
struct th_dvb_mux_instance *tdt_tdmi;
- int tdt_count; /* times seen */
void *tdt_opaque;
- int (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
- uint8_t tableid, void *opaque);
+ void (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
+ uint8_t tableid, void *opaque);
int tdt_fd;
struct dmx_sct_filter_params *tdt_fparams;
*/
typedef struct th_dvb_adapter {
- struct th_dvb_mux_instance_list tda_muxes_configured;
+ LIST_ENTRY(th_dvb_adapter) tda_global_link;
- struct th_dvb_mux_instance_list tda_muxes_active;
+ enum {
+ TDA_STATE_UNCONFIGURED, /* Found but not configured */
+ TDA_STATE_RUNNING, /* Configured */
+ TDA_STATE_ZOMBIE, /* Configured but not found */
+ } tda_state;
+
+ struct th_dvb_mux_instance_list tda_muxes;
th_dvb_mux_instance_t *tda_mux_current;
const char *tda_rootpath;
- const char *tda_info;
- const char *tda_sname;
-
- LIST_ENTRY(th_dvb_adapter) tda_link;
-
- LIST_HEAD(, th_dvb_mux) tda_muxes;
+ char *tda_identifier;
- int tda_running;
+ char *tda_displayname;
pthread_mutex_t tda_lock;
pthread_cond_t tda_cond;
const char *tht_name;
- LIST_ENTRY(th_transport) tht_global_link;
+ LIST_ENTRY(th_transport) tht_hash_link;
enum {
TRANSPORT_DVB,
int tht_tt_rundown_content_length;
time_t tht_tt_clock; /* Network clock as determined by teletext decoder */
- int tht_prio;
struct th_stream_list tht_streams;
th_stream_t *tht_video;
int64_t tht_pcr_drift;
uint16_t tht_pcr_pid;
- uint16_t tht_dvb_transport_id;
uint16_t tht_dvb_service_id;
+ uint16_t tht_pmt;
+
int tht_pmt_seen;
avgstat_t tht_cc_errors;
int64_t tht_dts_start;
- LIST_ENTRY(th_transport) tht_adapter_link;
+ LIST_ENTRY(th_transport) tht_mux_link;
+
+ LIST_ENTRY(th_transport) tht_tmp_link;
+
+ LIST_ENTRY(th_transport) tht_active_link;
LIST_ENTRY(th_transport) tht_channel_link;
struct th_channel *tht_channel;
void (*tht_stop_feed)(struct th_transport *t);
+ void (*tht_config_change)(struct th_transport *t);
struct th_muxer_list tht_muxers; /* muxers */
} file_input;
} u;
+ const char *tht_identifier;
+ const char *tht_servicename;
+ const char *tht_channelname;
const char *tht_provider;
- const char *tht_uniquename;
const char *tht_network;
+ enum {
+ /* Service types defined in EN 300 468 */
+
+ ST_SDTV = 0x1, /* SDTV (MPEG2) */
+ ST_RADIO = 0x2,
+ ST_HDTV = 0x11, /* HDTV (MPEG2) */
+ ST_AC_SDTV = 0x16, /* Advanced codec SDTV */
+ ST_AC_HDTV = 0x19, /* Advanced codec HDTV */
+ } tht_servicetype;
enum {
THT_MPEG_TS,
struct config_head *user_resolve_to_config(const char *username,
const char *password);
+extern inline unsigned int tvh_strhash(const char *s, unsigned int mod)
+{
+ unsigned int v = 5381;
+ while(*s)
+ v += (v << 5) + v + *s++;
+ return v % mod;
+}
+
+
#endif /* TV_HEAD_H */
t->tht_network = strdup("Analog TV");
t->tht_provider = strdup("Analog TV");
- snprintf(buf, sizeof(buf), "ANALOG:%u", t->tht_v4l_frequency);
- t->tht_uniquename = strdup(buf);
+ snprintf(buf, sizeof(buf), "analog_%u", t->tht_v4l_frequency);
+ t->tht_identifier = strdup(buf);
- transport_link(t, channel_find(channel_name, 1, NULL), THT_OTHER);
+ transport_set_channel(t, channel_name);
return 0;
}
th_v4l_adapter_t *tva = t->tht_v4l_adapter;
t->tht_v4l_adapter = NULL;
- LIST_REMOVE(t, tht_adapter_link);
+ LIST_REMOVE(t, tht_active_link);
t->tht_status = TRANSPORT_IDLE;
if(v4l_setfreq(tva, tva->tva_frequency))
return 1;
- LIST_INSERT_HEAD(&tva->tva_transports, t, tht_adapter_link);
+ LIST_INSERT_HEAD(&tva->tva_transports, t, tht_active_link);
t->tht_v4l_adapter = tva;
t->tht_status = TRANSPORT_RUNNING;