From d1f24092a224ea282ea2c37728118badbc7b15ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Mon, 7 Apr 2008 15:57:20 +0000 Subject: [PATCH] Work in progress on AJAX interface for tvheadend --- Makefile | 8 +- ajaxui/ajaxui.c | 28 +- ajaxui/ajaxui.css | 51 +++ ajaxui/ajaxui.h | 13 + ajaxui/ajaxui_config.c | 204 +--------- ajaxui/ajaxui_config_channels.c | 233 +++++++++++ ajaxui/ajaxui_config_dvb.c | 662 +++++++++++++++++++++++++++++++ ajaxui/ajaxui_config_transport.c | 411 +++++++++++++++++++ ajaxui/images/mapped.png | Bin 0 -> 448 bytes ajaxui/images/sbbody_l.gif | Bin 2176 -> 2497 bytes ajaxui/images/sbbody_r.gif | Bin 4672 -> 6160 bytes ajaxui/images/sbhead_r.gif | Bin 2814 -> 3124 bytes ajaxui/images/unmapped.png | Bin 0 -> 495 bytes ajaxui/prototype/prototype.js | 1 + ajaxui/tvheadend.js | 39 +- avgen.c | 5 +- channels.c | 29 -- cwc.c | 4 +- dvb.c | Bin 9007 -> 12394 bytes dvb.h | 14 +- dvb_dvr.c | 6 +- dvb_fe.c | 9 +- dvb_muxconfig.c | 248 ++++++++---- dvb_muxconfig.h | 17 +- dvb_support.c | 84 ++++ dvb_support.h | 16 +- dvb_tables.c | 256 +++++++----- file_input.c | 5 +- htsclient.c | 198 --------- http.c | 2 +- iptv_input.c | 19 +- main.c | 33 +- psi.c | 107 ++++- psi.h | 3 + transports.c | 278 ++++++------- transports.h | 15 +- tsdemux.c | 15 +- tvhead.h | 79 ++-- v4l.c | 10 +- 39 files changed, 2234 insertions(+), 868 deletions(-) create mode 100644 ajaxui/ajaxui_config_channels.c create mode 100644 ajaxui/ajaxui_config_dvb.c create mode 100644 ajaxui/ajaxui_config_transport.c create mode 100644 ajaxui/images/mapped.png create mode 100644 ajaxui/images/unmapped.png diff --git a/Makefile b/Makefile index bc368ef7..f94b2e4b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ SRCS = main.c dispatch.c channels.c transports.c teletext.c psi.c \ 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 @@ -33,7 +33,9 @@ SRCS += FFdecsa.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 @@ -41,7 +43,7 @@ CSSSRCS += ajaxui.css 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 diff --git a/ajaxui/ajaxui.c b/ajaxui/ajaxui.c index d1afd5d5..3b26e427 100644 --- a/ajaxui/ajaxui.c +++ b/ajaxui/ajaxui.c @@ -44,6 +44,9 @@ #include "obj/sbhead_l.gifh" #include "obj/sbhead_r.gifh" +#include "obj/mapped.pngh" +#include "obj/unmapped.pngh" + const char *ajax_tabnames[] = { @@ -194,6 +197,19 @@ ajax_menu_bar_from_array(tcp_queue_t *tq, const char *name, } +/** + * + */ +void +ajax_a_jsfunc(tcp_queue_t *tq, const char *innerhtml, const char *func, + const char *trailer) +{ + tcp_qprintf(tq, "%s%s\r\n", + func, innerhtml, trailer); +} + + /* * Titlebar AJAX page */ @@ -252,9 +268,13 @@ ajax_page_root(http_connection_t *hc, const char *remain, void *opaque) tcp_init_queue(&tq, -1); tcp_qprintf(&tq, + "" + /* "" + */ "\r\n" "" @@ -413,7 +433,13 @@ ajaxui_start(void) 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(); } diff --git a/ajaxui/ajaxui.css b/ajaxui/ajaxui.css index eee3df9b..1755a466 100644 --- a/ajaxui/ajaxui.css +++ b/ajaxui/ajaxui.css @@ -20,6 +20,19 @@ img { border: 0; } cursor:move; } + + +.normallist { + margin:0; + margin-top:10px; + padding:0; + list-style-type: none; + } +.normallist li { + margin:0; + padding:0px; + } + /** * Input classes */ @@ -29,14 +42,52 @@ img { border: 0; } 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 */ diff --git a/ajaxui/ajaxui.h b/ajaxui/ajaxui.h index 1a074761..8e94c416 100644 --- a/ajaxui/ajaxui.h +++ b/ajaxui/ajaxui.h @@ -50,7 +50,20 @@ void ajax_config_init(void); 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_ */ diff --git a/ajaxui/ajaxui_config.c b/ajaxui/ajaxui_config.c index b0bab8f7..5806ad27 100644 --- a/ajaxui/ajaxui_config.c +++ b/ajaxui/ajaxui_config.c @@ -30,12 +30,12 @@ #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", }; @@ -58,196 +58,6 @@ ajax_config_menu(http_connection_t *hc, const char *remain, void *opaque) return 0; } -/** - * Render a channel group widget - */ -static void -ajax_chgroup_build(tcp_queue_t *tq, th_channel_group_t *tcg) -{ - tcp_qprintf(tq, "
  • ", tcg->tcg_tag); - - ajax_box_begin(tq, AJAX_BOX_BORDER, NULL, NULL, NULL); - - tcp_qprintf(tq, - "
    %s
    ", - tcg == defgroup ? "" : "style=\"float: left\" ", - tcg->tcg_name); - - if(tcg != defgroup) { - tcp_qprintf(tq, - "
    " - "Delete
    ", - tcg->tcg_tag, tcg->tcg_name); - } - - - ajax_box_end(tq, AJAX_BOX_BORDER); - tcp_qprintf(tq, "
  • "); -} - -/** - * 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, "Updated on server"); - 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, "
    "); - - ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, "channelgroups", - NULL, "Channel groups"); - - tcp_qprintf(&tq, "
    "); - - tcp_qprintf(&tq, ""); - - 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, - "
    " - "
    " - "" - "
    " - "" - "
    "); - - ajax_box_end(&tq, AJAX_BOX_BORDER); - - ajax_box_end(&tq, AJAX_BOX_SIDEBOX); - tcp_qprintf(&tq, "
    "); - http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0); - return 0; -} - - - /* * Tab AJAX page * @@ -265,7 +75,9 @@ ajax_config_dispatch(http_connection_t *hc, const char *remain, void *opaque) 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; @@ -310,9 +122,9 @@ ajax_config_tab(http_connection_t *hc) 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(); } diff --git a/ajaxui/ajaxui_config_channels.c b/ajaxui/ajaxui_config_channels.c new file mode 100644 index 00000000..0c2a6584 --- /dev/null +++ b/ajaxui/ajaxui_config_channels.c @@ -0,0 +1,233 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#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, "
  • ", tcg->tcg_tag); + + ajax_box_begin(tq, AJAX_BOX_BORDER, NULL, NULL, NULL); + + tcp_qprintf(tq, + "
    %s
    ", + tcg == defgroup ? "" : "style=\"float: left\" ", + tcg->tcg_name); + + if(tcg != defgroup) { + tcp_qprintf(tq, + "
    " + "Delete
    ", + tcg->tcg_tag, tcg->tcg_name); + } + + + ajax_box_end(tq, AJAX_BOX_BORDER); + tcp_qprintf(tq, "
  • "); +} + +/** + * 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, "Updated on server"); + 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, "
    "); + + ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, "channelgroups", + NULL, "Channel groups"); + + tcp_qprintf(&tq, "
    "); + + tcp_qprintf(&tq, "
      "); + + TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) { + if(tcg->tcg_hidden) + continue; + ajax_chgroup_build(&tq, tcg); + } + + tcp_qprintf(&tq, "
    "); + + 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, + "
    " + "
    " + "" + "
    " + "" + "
    "); + + ajax_box_end(&tq, AJAX_BOX_BORDER); + + ajax_box_end(&tq, AJAX_BOX_SIDEBOX); + tcp_qprintf(&tq, "
    "); + 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); + + + +} diff --git a/ajaxui/ajaxui_config_dvb.c b/ajaxui/ajaxui_config_dvb.c new file mode 100644 index 00000000..a6016405 --- /dev/null +++ b/ajaxui/ajaxui_config_dvb.c @@ -0,0 +1,662 @@ +/* + * 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 . + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#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, "", 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, "
    Status:
    " + "
    %s
    ", + val2str(tda->tda_state, adapterstatus) ?: "invalid"); + tcp_qprintf(&tq, "
    Type:
    " + "
    %s
    ", + dvb_adaptertype_to_str(tda->tda_fe_info->type)); + + tcp_qprintf(&tq, "
    Tuner:
    " + "
    ...
    "); + tcp_qprintf(&tq, "
    " + "Edit
    ", 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, "
    "); + + LIST_FOREACH(tda, &dvb_adapters, tda_global_link) { + + tcp_qprintf(&tq, "
    ", + 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, "
    "); + tcp_qprintf(&tq, "
    "); + 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, + "

    Add new...

    ", tda->tda_identifier); + + if(result) { + tcp_qprintf(tq, "
    %s
    ", 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, + "
    %s
    ", + tda->tda_displayname); + + ajax_box_end(&tq, AJAX_BOX_FILLED); + + /* Type */ + + tcp_qprintf(&tq, "
    Model:
    " + "
    %s (%s)
    ", + 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, "
    Freq. Range:
    " + "
    %.2f - %.2f kHz, in steps of %.2f kHz
    ", + a, b, c); + + + if(tda->tda_fe_info->symbol_rate_min) { + tcp_qprintf(&tq, "
    Symbolrate:
    " + "
    %d - %d BAUD
    ", + tda->tda_fe_info->symbol_rate_min, + tda->tda_fe_info->symbol_rate_max); + } + + + /* Capabilities */ + + + // tcp_qprintf(&tq, "
    Capabilities:
    "); + + tcp_qprintf(&tq, "
    "); + ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, NULL, NULL, "Multiplexes"); + + /* List of muxes */ + + tcp_qprintf(&tq, "
    "); + + tcp_qprintf(&tq, "
    " + "Freq.
    "); + tcp_qprintf(&tq, "
    %s
    ", + "Status"); + tcp_qprintf(&tq, "
    %s
    ", + "State"); + tcp_qprintf(&tq, "
    %s
    ", + "Name"); + tcp_qprintf(&tq, "
    %s
    ", + "Services"); + tcp_qprintf(&tq, "

    "); + + + tcp_qprintf(&tq, "
    ", + tda->tda_identifier); + tcp_qprintf(&tq, "
    "); + + + + ajax_js(&tq, + "new Ajax.PeriodicalUpdater('dvbmuxlist%s', " + "'/ajax/dvbadaptermuxlist/%s', {method: 'get', frequency: 5}) ", + tda->tda_identifier, tda->tda_identifier); + + tcp_qprintf(&tq, "
    "); + dvb_make_add_link(&tq, tda, NULL); + tcp_qprintf(&tq, "
    "); + + ajax_box_end(&tq, AJAX_BOX_SIDEBOX); + tcp_qprintf(&tq, "
    "); + + /* Div for displaying services */ + + tcp_qprintf(&tq, "
    "); + tcp_qprintf(&tq, "
    "); + + 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, "
    " + "Add new %s mux
    ", + dvb_adaptertype_to_str(tda->tda_fe_info->type)); + + tcp_qprintf(&tq, + "
    Frequency (%s):
    " + "
    " + "" + "
    ", + fetype == FE_QPSK ? "kHz" : "Hz"); + + snprintf(params, sizeof(params), + "freq: $F('freq')"); + + + /* Symbolrate */ + + if(fetype == FE_QAM || fetype == FE_QPSK) { + + tcp_qprintf(&tq, + "
    Symbolrate:
    " + "
    " + "" + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", symrate: $F('symrate')"); + } + + /* Bandwidth */ + + if(fetype == FE_OFDM) { + tcp_qprintf(&tq, + "
    Bandwidth:
    " + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", bw: $F('bw')"); + } + + + /* Constellation */ + + if(fetype == FE_QAM || fetype == FE_OFDM) { + tcp_qprintf(&tq, + "
    Constellation:
    " + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", const: $F('const')"); + } + + + /* FEC */ + + if(fetype == FE_QAM || fetype == FE_QPSK) { + tcp_qprintf(&tq, + "
    FEC:
    " + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", fec: $F('fec')"); + } + + + if(fetype == FE_OFDM) { + tcp_qprintf(&tq, + "
    Transmission mode:
    " + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", tmode: $F('tmode')"); + + tcp_qprintf(&tq, + "
    Guard interval:
    " + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", guard: $F('guard')"); + + + + tcp_qprintf(&tq, + "
    Hierarchy:
    " + "
    "); + + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", hier: $F('hier')"); + + + + tcp_qprintf(&tq, + "
    FEC Hi:
    " + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", fechi: $F('fechi')"); + + + tcp_qprintf(&tq, + "
    FEC Low:
    " + "
    "); + + snprintf(params + strlen(params), sizeof(params) - strlen(params), + ", feclo: $F('feclo')"); + } + + tcp_qprintf(&tq, + "
    " + "" + "
    ", 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, "
    " + "No muxes configured
    "); + } else LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { + + tcp_qprintf(&tq, "", o ? " style=\"background: #fff\"" : ""); + o = !o; + + tdmi_displayname(tdmi, buf, sizeof(buf)); + + tcp_qprintf(&tq, "
    "); + tcp_qprintf(&tq, "
    " + "%s
    ", tdmi->tdmi_identifier, buf); + + tcp_qprintf(&tq, "
    %s
    ", + 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, "
    %s
    ", + txt); + + txt = tdmi->tdmi_network; + if(txt == NULL) + txt = "Unknown"; + + tcp_qprintf(&tq, "
    %s
    ", + txt); + + n = 0; + LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) + n++; + + tcp_qprintf(&tq, "
    %d
    ", n); + + tcp_qprintf(&tq, "
    "); + } + + 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); + +} diff --git a/ajaxui/ajaxui_config_transport.c b/ajaxui/ajaxui_config_transport.c new file mode 100644 index 00000000..32f7411b --- /dev/null +++ b/ajaxui/ajaxui_config_transport.c @@ -0,0 +1,411 @@ +/* + * 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 . + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#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, "\r\n"); + + /* Top */ + + tcp_qprintf(tq, "
    "); + + tcp_qprintf(tq, "
    "); + + tcp_qprintf(tq, "
     
    "); + tcp_qprintf(tq, "
    SID
    "); + + tcp_qprintf(tq, "
    Crypto
    "); + + tcp_qprintf(tq, "
    Type
    "); + + tcp_qprintf(tq, "
    " + "Service
    "); + + tcp_qprintf(tq, "
     
    "); + + tcp_qprintf(tq, "
    " + "Channel
    "); + + tcp_qprintf(tq, "
    " + "Bah
    "); + + tcp_qprintf(tq, "

    "); + + + LIST_FOREACH(t, tlist, tht_tmp_link) { + tcp_qprintf(tq, "", o ? " style=\"background: #fff\"" : ""); + o = !o; + + tcp_qprintf(tq, "
    "); + + tcp_qprintf(tq, "", t->tht_identifier, t->tht_identifier); + + tcp_qprintf(tq, "
    %d
    ", + t->tht_dvb_service_id); + + tcp_qprintf(tq, "
    %s
    ", + t->tht_scrambled ? "CSA" : "Free"); + + tcp_qprintf(tq, "
    %s
    ", + transport_servicetype_txt(t)); + + tcp_qprintf(tq, "
    %s
    ", + t->tht_servicename ?: ""); + + tcp_qprintf(tq, + "
    " + "" + "
    ", + t->tht_identifier, t->tht_identifier); + + tcp_qprintf(tq, "
    ", + 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, "
    "); + + + + tcp_qprintf(tq, "
    " + "" + "
    ", t->tht_identifier); + + tcp_qprintf(tq, "
    "); + + /* Extra info */ + tcp_qprintf(tq, "
    ", + t->tht_identifier); + + tcp_qprintf(tq, "

    "); + + tcp_qprintf(tq, "

    "); + tcp_qprintf(tq, "
    " + " 
    "); + tcp_qprintf(tq, "
    " + "PID
    "); + + tcp_qprintf(tq, "
    " + "Payload
    "); + + tcp_qprintf(tq, "
    Details" + "
    "); + tcp_qprintf(tq, "
    "); + + + LIST_FOREACH(st, &t->tht_streams, st_link) { + tcp_qprintf(tq, "
    "); + tcp_qprintf(tq, "
     
    "); + tcp_qprintf(tq, "
    %d
    ", + st->st_pid); + tcp_qprintf(tq, "
    %s
    ", + 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, "
    %s
    ", + extra); + + tcp_qprintf(tq, "
    "); + } + + tcp_qprintf(tq, "

    "); + tcp_qprintf(tq, "\r\n"); + + } + tcp_qprintf(tq, "
    \r\n"); + + tcp_qprintf(tq, "
    "); + + tcp_qprintf(tq, "
    Select:
    "); + + 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, "
    \r\n"); + + tcp_qprintf(tq, "
    "); + tcp_qprintf(tq, "
     
    "); + + ajax_a_jsfunc(tq, "Map selected", "selected_do('map');", " / "); + ajax_a_jsfunc(tq, "Unmap selected", "selected_do('unmap');", ""); + + tcp_qprintf(tq, "
    "); + + tcp_qprintf(tq, ""); + 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='" + "%s" + "';\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); +} diff --git a/ajaxui/images/mapped.png b/ajaxui/images/mapped.png new file mode 100644 index 0000000000000000000000000000000000000000..a4494940055de713b981bfe80c5fea20a16d8c0c GIT binary patch literal 448 zcmV;x0YCnUP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOD# z5)K&$7(cE600BTrL_t(2&y|utNCI&b$3GXgv^F~xbb^yNBo2a02){WIX$@{ID^1&YN!Si3JHFKlRDVa*q^C2Y`F`~6Y2*)`0?K7eZTMhc*^_RD*#f; zsqNS-J4eORx~fJ#0A(~9u9Z#Wa~Ye@<IMDlw^pEmUeKl2y}Gp# zrrxLp{ literal 0 HcmV?d00001 diff --git a/ajaxui/images/sbbody_l.gif b/ajaxui/images/sbbody_l.gif index 12806b126468cdb0e532e2fc45bb758815ac367c..a7adf2d3577fe5837025be1a9c9cb783e8564a40 100644 GIT binary patch delta 1172 zcmZn=JSfcL?&)S>nJCB5!h9@nBG0e-{{ss|VAks}00GEZ4DA0VFJ%13^iOf}LZ-jW z|Bm0CypZ`1%Rh7LkCPX&{AT^Pr}_M!$qQMt>n#onL$M zFj*jZw8B+?-NmlhIg5l3bGU0xE9?t>vPgDw$8lY^i9Ize$#VZ^xUudF>~9E3mOp>P z&64Xvy&)d-9{!Lv?i z;@XmjikzA!!!KQ$IA0}ID|V)L*tJd5H~e`jTq@};G0$km4w+^8lRe#2s)A;oyP{%} z|MN^*Q1Gm4Ju1fRE6-*XZJvDNPnw!5=b7wD#k216sH-2ZUwNu%+2=VoS=4QgMta39 z3ZD0NkGl0+$@8_RKFxS`Cf)w^PygtB!3)0JNp-y2>0hU|Wri!u3rE>qeoa!B7x2ce zu+x3^Ex=g8c7SD((gG&I3`P!%gnx}Z8l~z?5)O4Pn7bygl#$Ql0L#8tjl5GBSW~ZRF^U{u*qWrkB7dQQlW9RS$Au$nic=X}qy(Co z|6CPd@n}tyXK3OM31#K8cw;b6DwJ!>Ru)HvhLqV|VY}NBIHW9`m|N;6Fm&cPt!7)W zS}VotD&LL0k=V{HO&cQkL00<-#oM*bNWLKoUx=PdGdiBjJXB(UWx$BnZG`9c(!9e0!qY?eKs zxMX$zVCJn@ z-?dOXo<%>Ak*8v#h`|R2E{6xqG75*-bP8Bxg_Dj-_!UgBmSI%(Sis>X*Tk+@@4%vd ze%f)?gr+H$H4U5|2T$>HFtR!rw0uAGoiiZMX@>CXM0OQL#?*C2Y$_ibly>Yq;Qwsn zoTGE16mCnN_n6TT-}@l#2b8gi13@@CjF1WOqHLMm@cpZ89)-~n{>*pNSNn_IcVs+J@ yEpP2yhRnp%KW}KhGnq4WZ%$gX_${$_C7bGFvvdAO-oDhfd3!%^o(2mOgEataF)sK3 delta 848 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 delta 4394 zcmc&$Ydq7B!~O2Nx#ZSTzg!bS5?UoOl#nFhSN-LdRJsvKB}OLqyO~S2x#u!7GsEm= z*2ZY$R>`gMlaNZH#K@lC|9Stse$I>Y=DaxP#W|nP!60~g3a3sxT3BB31Uh+u9wTIGve!;_DTJQv@0<{_?$iq!7wpNQJIZD`^}Q;l186-$wP@yBHUGpAf5lN-6j z?x$y-ITt(UpL>NdedzH{)bizZoymRFt~2znu2hRm{G}eciDUCg$thOxnbrj>hFiaP>BmO%soK)t}ro>*)+>deiNg0b}abT zi>mW=?@{>W`M1<;`mhF@9$eUT_sjdohmL;#z}qcWa^*y{|0B%Ze>pjtxjg$CZ(r*A z=jqS+k*0VSrH%dQ%tU+o?tVk-wdFY}oLm3qx_D)=u|3;OvbG^!&HT{cFZqq$+yur} zF2}+s=KfD)v%>x36g=qu@o=LR|AZZK=2sF`#~xlu(xkUsN!G^xx{{(tn;CKU)bW@~ zD=RLZNHQ_Ojs&cGNoVDa z$dk9L+?@Ps_1z0cRdGQ+NvF@D3$7N_D&cNarz+wD>6VIw5TT_)QP}Wj`Qq@I+aq}h z5^=Wl+vJwHGJ>r0T>0b=oNiW%xwyLGXvNH2<$XKxr`)_7qV#fnxVLOgNs?S3u{g^) zu)4Ygd7DHs7X{WObLGC2(fY(U>*+J8L6nCJqM#z~=GYA?zxwo@#_MRU^gD$j)4JK1;qQoDS}7jL<@V zz0|E6bSmt{lGnVRD60hByY#@c{Z6UqKO&b~z2^Iq@AruZHTU$R78D+Y+HL(-+8?8? zXgri*WMljaiwrh??eAP{{HD;m?fRg1m+;}+hR2HZz^mDpyhm^BPkA_Y@zH3&%R6`5 z!Y9JmXFv5nIAitbJ!<^j%~#PHso|3uimr@=sNJR!Q)%+IBRC#M&M{vyJ zXAKAR#ZOtg{nwXz9MFHzjQng#*zfNj2%DQ<)HXbJ@@5s`AyRkTB+A`6Qit64C`C_V zl~Rsw3CWH--Bk7)A#0%+zBf+GS60@(5VH-txZA+`pL+n2Pi>?o21My7^cx7&NSX1) zO*)c5qM)ymn*3x+2flAvpi^=>#nIPgTP`tE|Iod&PSLtoiVQo^!48dSd2HRC2WmP^ z0$mLcOcjYao*G8j9|ta3Kf66d6z*zl%)D`Mk7oU{&@$^}R`|q)UCZ5nSufplv(Wsw z+va1}iKGfl(gsX-`)KOPLp1lO=*n}N`%b&K)3B*>0mFz?i?d-%lEY6oD)sF4IXhu! zd5}~+quXJk|ESyNQ_$os#@BFIkD(+ZR1^=!3`eTWLzT;T<~WO%MRNCn{kD)TG!`z;~L{8`=gQ4=IggI?}@u{?> z!HUQZ;;tjeQ(64MXTh*q^^+(^EOxNc&*AgV|D>HP@EEK*m+*P}b&&(!V6fV;<1_rB zoI}aVKn)T`QcOhJSMUdjCJrRIe3V@^cA!=#fh1E!IzjRtASp{a=AkVjTZ+LzofIqx z=$t=JTj(dRqV0qJyf!*cXZ6>AiLYCmMjm6u^;6yn>Q;WDY`Ew88wR1|WftVH}iTD^cgZx)5@!}c|mL+fW92$nH$(}Nt7j$IC`6bgeo-$M! zWHzt#l4UC}?QbR=UKs0TAA)}G>F7DMRME@Pw{Pfvy?$^dthY@qzM*s4>Y&)Jm#Zjf z;Qx$5Y%2G*L!i`l80P@^v4{7|j>_4wVJh9!^ZauhRl?M>HkFI-;lJZk>1NR;O6Pk7 zgJ`g^$$?|6yr-w5)2@+vVPpT!Wl<+HuCYGAdcV3rB&_E*k{(1GY2}K#D&+PemmvXe ze=#rEX)RsClBcyxnqK%>(yA*nj1278Qrx(-R2um@$sKp@vR{5ayC@UUdZ#yln}oZR zIAi9M(-Tq>K=^2!d1Q~MuMvN^7*{ciwz(+R7b84Z3RP6F{%~g?fxEq8d;KZf!{Srv zF=pB|k*x~O`1~C1Po2Z{w@-$#US)2W5p_P#{k?wfbv0d&biVksv&1Bsd~Aqx?Ooxy zSI9j{)o;jmgGHB?k@@t^hxTELhF3p`-tuFvQ*$Nt@M|)PqrHv^O%J8M-O{)^7LMkJ zH;{sZ?QZ$Fe()Qkv29#0rr*n*mY!w2s?QE(zj){7bCmrLrkOd$8`~B6HK@`HSL37Q{LRdeccz+Oinpw&ASueOy3L`w=q3{`k2&BG19E zKFxt*e?%NVN_@(vekw+6R6N~TP{kHMe=J($Nf?xOjwq81Q zYVXCtyTw1%j{NaGQ}gT9ag|@5Zo3{LvMqNsQ62{QwmDqByz4DntC-Y;{HkNe{vvc; zK@x1<1b%m)xYo5oUuG_ZOO}P(3V-W=Z2O)V!>*kz(UI_9Eqsr?Xg0O|!(7|N+#fn< zl?|feWgXG1JfJ%#gz1Grws0n4`T(%S4iP6U8w&xGqaPp|BKx8?f%4stKf9Hp-n_;A zQW}XiGM$W5;racnJldum+%E2&-PEx6Kk@y<){W7vrK_z+xeI7*s19!uwhuTan{YX5 zu7AsK)?U6sATM5{*%oGmfXE)3e1e7;^44`^Bu7m#PyS+nQc6w|u}xX*Z$W$Nk_w<= z3!REZ!^>j6zC}wOMF9h`N-2@6GJqi*glj|1{L%V6m>wJg1HhI6$h+3qpKDhi+}+fYHSNR68cze4v=BQfU$|O zswo71Vv;HvVugXeX$3z$N}2{z6?jPyL|Ub1T6IJku`rFqNF$G=QC8Eas_8U~^k&a= zdPF*-FrCFn=ZvItSJQc_8GKbh69WhffvQ5F(d$llI%*fst z$=+l@9N`!w0t1bNohpNzhQp3cLQnWZ>@m@%R4{{)e7ifQygO6A45H4=(HzauTFcQ^ z!|GaM^}MhKkyt|<)`*EU9>tojVG(M%hb(i=ymHMWb1iVW>Hy4o3(R6M7j0V$I};1B zw1u2>hYihvQUKIE6g1REX)@3Xp*f-yh(~0;CobQcneRKA@3)rkuU2r?vLL{#;6`M@ zEnGn$v*6BXLC9J`sM^yo%cl>#o~puOCt~yEy&yINPi+t|2OelP30hI}A`2g9-2(U6 z$`fZLG4f>@f5CBBOB_1S3s(?{E5zaOOkB|@u4D~YriQPu#8-OZt0VD59G=9)lSlEC zH9XY|Vu>K+-Oh9HhwS8mPTCMtYRXyM6KPDm&cKsum9&$qg_@xd5woaww5V^bXh5xa z$g=pgSMhLU@d&PXj9EM}TKs;kcv7uo%CcnIt7Im!WDbohQPGBe-3L9qSaO&L9=3%l z^9WW{@OT7x&>h^a4eHv0GyvS{pMxHOWa9ueOqm?3Oku1{Nn8e3FIPsE@9-|)i7Hpc zm#eeNHOI=e#O2!R6}rd@J?{zwRD~fPVrEjfdf3%VcvOllEC@Swwm63}@L(1k-h z(A_Z$Zy~m*GATr*gLkDPs?rHx>CCEh8LM;^SGucLokLb#@UHSeRe9p8yjfMgV^x0Q zDu4CrtKPst7U<;s>@2fXe;~nx2a{0+WmQvEn81m^44KfH2z(8SRTDi{6C3ocX*S9C~`NREMk#+ z$H;wR@_=)lKBE3L9yCEfssKRRA28xQ+r@|>ktCVN=Sf|KXkdFNWe!i7XHmY6Q5M9M zMfHXyWW!JIh80x9D!xI?YS97%2os8e78182@+w>kNzr6KloH^I;Z5F$)PyJeyfG&MaAHJ~@;8 zKpuRy4ur2mNXW+XTPmZzkRDs55!Kl=D|WLFn;ykx5ZEj>n={VluCsX>9KIE&!-pe` z;&c-@A~vUYoYS|?X-Q}8#*m2nf#E3B$sl>uIExaLQ5DfPsllB(ZpEGU;m$;H=Lp<+ zHuvi|cVV5osL{S;)&A3`eI=@WmC!C`w{MKMPp7v63cT}{`F}1ZkToh#ePK0d0MJ~Y zZQ5vEYrdW@-yoWASj0E-eJ(Y@myT}ULgpQcPBZl_7MMp1EQ$n3j=*|CaBM?htJz^^ z-QnQd;TYZFRMg?j>2R6oaNX!|H|~&$<{^v)pS&qGvI1{Tr|(3k-$tjurtqq@Fu+%M zBU*T?NEpZw-kA`FYzRX&yTYuy9{6@Chevl^H5Qh(PA&=;`X~?CtLF@bU8V^!4`l`1$(#{Qds_00RmfNU)&6g9sBUT*$ED zx*h0#jdOKqhsBE+Giuz(u?VBbk03*e97&T(0vr`5NOt0ADdIu2sL`WHlPX=xw5ijl zlY0Um6`M4QTetof-(EC_t5u>Pc=PJr%eRxS0wWcfAidrU+)#e@@T=Uot{g%?F?vLKSghaiS1 z;)o=cXyS<|rl{hIEVk(4i!jD0E)MThAHNlWR_{>nP{e|=9+A_>E@eo#wq8V zbk=F-op|P{=bn7_>F1w-dWppYgg!99p)<^3!4P!Rafc1wRe4cRtsG$KrI==_>86}^ zf9mO{poS{ysHB!^>Zz!vs_Lq&w(9Duu*NFuthCl@>#exvs_U-2_PQw;z=DB9p$_Db zLmO>az=ROlc~?{vxwKNmwA5B>?X}outL?VjcI)l8;D#&ixa5{=?z!lutM0n&w(IV@ z@Ww0ey!6&<@4fiut8ca`uj8z6xN7#Kug!J#)Zz`|PyW zZu{j{I9FaP}X*FU`$T1ekQ3kAS|00n8#K>z~4f#WSq z3CkPLEdId220HM6fe?(K1Sd$r3R>`j7|fssH^{*bdhmlF450`|NWv1D@PsH#p$b>X z!WO#lg)oev3}=|Z39R4`IOtyrHUI-1L;zuGFhK^E#)C;gfrdy-q7s+L#3nlNiBOE9 z6sJhVDq8W1B;3OUCXj_K6rcqu_<#-yH-iCWAOlKRLU=5HvOp}l@r`hdqa5c*$2!{a zj(E%?3UBxa2<)$a0uVtSVlV?NydVQYXc%08a6~;i@{y2?q$DRv$x2%Ck|w<21pNLO zhZbxB0c&7E5lY~IIlLfwK{(BLFv-eRy7HB7?Km;sszyK^T1@1^83K|Wm zOlL~dn%Xp^BQ5C(48Q_nvNVk^jpQ9WP< z5cr{g3@82ON?S@-sp|Exh)t|w(dhvqfS?a!5Q6~#K)|0CcCnbvtY$aM%Ek(y4`+}p zWjWB#%X0R#s7OMXq$GOI_3|m$@h)Kx~_TfQ30gHM-ReuXx9s)*dX^wb}&$cfFg& z@R0Yt@Qtrg&5PaiHX#7)Riu3P%isQPl)egZf_?FO%$NH2zz9z8GyyE&{_PStkp@<< zgeOelRx+3XPe{OeziZnHd-%g3zT<^6yy5nCIK(JUv5KQOVhwkf!z+%ljAsnP7MECm zjVGqDj(5!C4%+y|ChoD2hfHJ${y4wjEwYlAEaM}?n8{F%GKifVWGP$u$_S=1jI+LDyMw5B(m$4qnj)1ZE%r$bHZQbTdn zrcSl0XNc-myZY5BY&EQBP3sTJy4JYPbp>y&>t6f%54`@huxTCaVg4I?)x|!xvPqrn zWjlM*&3?ADB^~W)TYJ&fzP7dno$YOVJI~$zwz%aS?s1zt&E-C~y1kt4b-UYt%I$u) zyq6sBdD~ma^}e^hX`JtU`&-5R{(`o-X+wabIr04#9?N9Ez;5l0N!5jXH zg+ILFrI`4|JHCmHf4t<882QOtzQ%vQahNa9d0JM!^PoTB=R;3=5stop^r-i7=7GNX z)Vm&%r+>ZdDH!|N+x~&IzrF5ruY23`zW1{KJ@8#G{Nba%_{Jwa@{>Xmb!4figFN6zFmmc!4UH zff{IX9N2*(_kkejaUwW^8driQXmKc*f)claEa-49_<{ zgZkEkK4@=17=-dRghc3WMtFqkmV`=ZZcNyO;`W44=xtIsh1ynbg;r>7SeS*CWwQ`Ts{YQg=mPrM~Fekh>eJdYNvT^2Z^cIhn5Ixn3##2W@do^06XB(HueAj delta 2198 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 Y`|a7C_}l|`^uBi`@Pp6F+y(*wJ9ScK-T(jq diff --git a/ajaxui/images/unmapped.png b/ajaxui/images/unmapped.png new file mode 100644 index 0000000000000000000000000000000000000000..0f2f7388a200981b4735b3ac4a923fd42b7d2cce GIT binary patch literal 495 zcmVPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOD# z5)Lp$s;9yL00D1FL_t(2&yAA3OM+1pho9RAK|usTQ%w$95QYtfL*Np^yCq^xfn;40 zGiVL^7m83`KXB8Q42KGnqA#KkxIrz|5Dh^s6%^G_(A(k*#+%WZ&biO|{SJqFL}{bw zN@Y@tDlr|2Hai@huYdu#fg9i+XurI?c%ENec&Vlh1w*~UAMh#fpytvv&C*aP6xiA> z#r2pz1AGEb;H*4sREA}byMJJ6WMtT)&1SP4og5(i0iUtB5GN9yv@~5^-tht_prio = 100; t->tht_type = TRANSPORT_AVGEN; t->tht_start_feed = avgen_start_feed; @@ -117,9 +116,9 @@ avgen_init(void) 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); } diff --git a/channels.c b/channels.c index db6bb520..b2b56f2b 100644 --- a/channels.c +++ b/channels.c @@ -43,7 +43,6 @@ static void channel_group_settings_write(void); 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; @@ -234,8 +233,6 @@ service_load(struct config_head *head) 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) { @@ -382,32 +379,6 @@ channel_group_by_tag(uint32_t tag) 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 diff --git a/cwc.c b/cwc.c index 8a30ace2..8655a012 100644 --- a/cwc.c +++ b/cwc.c @@ -500,7 +500,7 @@ cwc_dispatch_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len) 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; @@ -509,7 +509,7 @@ cwc_dispatch_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len) 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); diff --git a/dvb.c b/dvb.c index 3c47985c335a9416d9de528a458eea6ab102d2ee..1b7c724e489aeae6f6a063b83aaf9111fa852415 100644 GIT binary patch literal 12394 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 diff --git a/dvb.h b/dvb.h index 72946499..4e01653d 100644 --- a/dvb.h +++ b/dvb.h @@ -28,9 +28,8 @@ enum polarisation { #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); @@ -48,7 +47,14 @@ void dvb_fe_start(th_dvb_adapter_t *tda); 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_ */ diff --git a/dvb_dvr.c b/dvb_dvr.c index c07e15bb..dd5baff9 100644 --- a/dvb_dvr.c +++ b/dvb_dvr.c @@ -71,7 +71,7 @@ dvb_dvr_process_packets(th_dvb_adapter_t *tda, uint8_t *tsb, int r) 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; @@ -120,7 +120,7 @@ dvb_stop_feed(th_transport_t *t) { 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; @@ -199,7 +199,7 @@ dvb_start_feed(th_transport_t *t, unsigned int weight, int status) 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); diff --git a/dvb_fe.c b/dvb_fe.c index 1d11f9f6..639e8eee 100644 --- a/dvb_fe.c +++ b/dvb_fe.c @@ -83,7 +83,7 @@ dvb_fe_manager(void *aux) 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; @@ -105,9 +105,8 @@ dvb_fe_manager(void *aux) 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", @@ -160,6 +159,8 @@ dvb_fe_manager(void *aux) 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; @@ -223,7 +224,7 @@ dvb_tune_tdmi(th_dvb_mux_instance_t *tdmi, int maylog, tdmi_state_t state) 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 */ diff --git a/dvb_muxconfig.c b/dvb_muxconfig.c index 28451878..4cf7f9f3 100644 --- a/dvb_muxconfig.c +++ b/dvb_muxconfig.c @@ -30,86 +30,10 @@ #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 }, @@ -148,7 +72,6 @@ static struct strtab modetab[] = { { "AUTO", TRANSMISSION_MODE_AUTO } }; - static struct strtab guardtab[] = { { "1/32", GUARD_INTERVAL_1_32 }, { "1/16", GUARD_INTERVAL_1_16 }, @@ -165,6 +88,174 @@ static struct strtab hiertab[] = { { "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 @@ -323,3 +414,4 @@ dvb_mux_setup(void) dvb_muxfile_add(ce->ce_value); } +#endif diff --git a/dvb_muxconfig.h b/dvb_muxconfig.h index 64684dc7..dcdd20fc 100644 --- a/dvb_muxconfig.h +++ b/dvb_muxconfig.h @@ -19,6 +19,21 @@ #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 */ diff --git a/dvb_support.c b/dvb_support.c index 80bf523e..3611e3da 100644 --- a/dvb_support.c +++ b/dvb_support.c @@ -29,8 +29,12 @@ #include #include +#include + #include "tvhead.h" #include "dvb_support.h" +#include "strtab.h" +#include "dvb.h" /* * DVB String conversion according to EN 300 468, Annex A @@ -205,3 +209,83 @@ dvb_convert_date(uint8_t *dvb_buf) 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; +} diff --git a/dvb_support.h b/dvb_support.h index 2cef686b..3d0c3ade 100644 --- a/dvb_support.h +++ b/dvb_support.h @@ -34,6 +34,7 @@ #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 @@ -41,15 +42,6 @@ #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); @@ -61,4 +53,10 @@ int dvb_get_string_with_len(char *dst, size_t dstlen, 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 */ diff --git a/dvb_tables.c b/dvb_tables.c index 383cd622..46a2ab20 100644 --- a/dvb_tables.c +++ b/dvb_tables.c @@ -92,10 +92,7 @@ dvb_table_recv(int events, void *opaque, int fd) 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); } @@ -107,9 +104,9 @@ dvb_table_recv(int events, void *opaque, int fd) */ 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; @@ -125,7 +122,6 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, 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); @@ -199,7 +195,7 @@ dvb_desc_service(uint8_t *ptr, int len, uint8_t *typep, /** * 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) { @@ -227,11 +223,8 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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; @@ -246,12 +239,13 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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(); @@ -307,14 +301,13 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, } 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) { @@ -335,13 +328,11 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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; @@ -385,11 +376,10 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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++; @@ -399,70 +389,51 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, l--; } + t = dvb_find_transport(tdmi, service_id, 0); + if(t == NULL) + break; + + change |= + t->tht_servicetype != stype || + t->tht_scrambled != free_ca_mode || + strcmp(t->tht_provider ?: "", provider) || + strcmp(t->tht_servicename ?: "", chname ); + + t->tht_servicetype = stype; + t->tht_scrambled = free_ca_mode; + + free((void *)t->tht_provider); + t->tht_provider = strdup(provider); + + free((void *)t->tht_servicename); + t->tht_servicename = strdup(chname); + + if(t->tht_channelname == NULL) + t->tht_channelname = strdup(chname); } break; } len -= dlen; ptr += dlen; dllen -= dlen; } - - - if(chname != NULL) switch(stype) { - - case DVB_ST_SDTV: - case DVB_ST_HDTV: - case DVB_ST_AC_SDTV: - case DVB_ST_AC_HDTV: - /* TV service */ - - 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) */ - } - } - } - break; - - } } - 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]; @@ -472,21 +443,21 @@ dvb_pat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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) { @@ -514,32 +485,86 @@ dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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++; @@ -550,7 +575,7 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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); @@ -562,7 +587,42 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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; + } + } } @@ -570,31 +630,35 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, /** * 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; } @@ -628,38 +692,38 @@ dvb_table_add_default(th_dvb_mux_instance_t *tdmi) 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); } @@ -680,5 +744,5 @@ dvb_table_add_transport(th_dvb_mux_instance_t *tdmi, th_transport_t *t, 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); } diff --git a/file_input.c b/file_input.c index e3f231d1..30e4d315 100644 --- a/file_input.c +++ b/file_input.c @@ -112,7 +112,6 @@ file_input_init(void) 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; @@ -156,9 +155,9 @@ file_input_init(void) 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); } } diff --git a/htsclient.c b/htsclient.c index 95a070ec..f240c658 100644 --- a/htsclient.c +++ b/htsclient.c @@ -144,203 +144,6 @@ clients_send_ref(int ref) } } -/* - * - */ -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 ?: ""); - - 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; -} - - /* * */ @@ -697,7 +500,6 @@ const struct { 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 }, diff --git a/http.c b/http.c index f904874f..b1d9d375 100644 --- a/http.c +++ b/http.c @@ -543,7 +543,7 @@ http_con_parse(void *aux, char *buf) 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: diff --git a/iptv_input.c b/iptv_input.c index eadf3741..a43213ad 100644 --- a/iptv_input.c +++ b/iptv_input.c @@ -222,12 +222,10 @@ iptv_configure_transport(th_transport_t *t, const char *iptv_type, 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); @@ -240,12 +238,13 @@ iptv_configure_transport(th_transport_t *t, const char *iptv_type, 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_channel = channel_find(channel_name, 1, NULL); - LIST_INSERT_HEAD(&iptv_probing_transports, t, tht_adapter_link); + t->tht_channelname = strdup(channel_name); + + LIST_INSERT_HEAD(&iptv_probing_transports, t, tht_active_link); startupcounter++; if(!dtimer_isarmed(&iptv_probe_timer)) { @@ -277,7 +276,7 @@ iptv_probe_done(th_transport_t *t, int timeout) 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" : ""); @@ -285,9 +284,9 @@ iptv_probe_done(th_transport_t *t, int timeout) 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) diff --git a/main.c b/main.c index 8fe50036..e33095ec 100644 --- a/main.c +++ b/main.c @@ -183,6 +183,8 @@ main(int argc, char **argv) settings_dir = config_get_str("settings-dir", NULL); + settings_dir = NULL; + if(settings_dir == NULL) { settings_dir = "/tmp/tvheadend"; sys_warning = @@ -204,6 +206,15 @@ main(int argc, char **argv) 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); @@ -233,7 +244,7 @@ main(int argc, char **argv) subscriptions_init(); - htmlui_start(); + // htmlui_start(); if(doajax) ajaxui_start(); @@ -369,26 +380,6 @@ utf8tofilename(const char *in) } - - - - -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 ""; - } -} - - FILE * settings_open_for_write(const char *name) { diff --git a/psi.c b/psi.c index d41c91ed..2f18b8ca 100644 --- a/psi.c +++ b/psi.c @@ -23,6 +23,7 @@ #include #include +#include #include "tvhead.h" #include "psi.h" @@ -90,7 +91,7 @@ psi_parse_pat(th_transport_t *t, uint8_t *ptr, int len, 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; @@ -204,7 +205,7 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid) 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; @@ -507,3 +508,105 @@ psi_caid2name(uint16_t caid) { 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); + } +} + + diff --git a/psi.h b/psi.h index 13b6d903..978fb9f1 100644 --- a/psi.h +++ b/psi.h @@ -43,4 +43,7 @@ int psi_build_pmt(th_muxer_t *tm, uint8_t *buf0, int maxlen, int pcrpid); 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_ */ diff --git a/transports.c b/transports.c index 19eeb847..7c531edb 100644 --- a/transports.c +++ b/transports.c @@ -39,6 +39,7 @@ #include "tvhead.h" #include "dispatch.h" #include "dvb_dvr.h" +#include "dvb_support.h" #include "teletext.h" #include "transports.h" #include "subscriptions.h" @@ -51,12 +52,18 @@ #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) { @@ -73,6 +80,8 @@ 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) @@ -200,12 +209,70 @@ transport_start(th_transport_t *t, unsigned int weight) } +/** + * 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 */ @@ -251,7 +318,7 @@ transport_compute_weight(struct th_transport_list *head) 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; @@ -261,7 +328,7 @@ transport_compute_weight(struct th_transport_list *head) } - +#if 0 static void @@ -329,35 +396,44 @@ transport_monitor(void *aux, int64_t now) } } +#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 + */ +th_transport_t * +transport_find_by_identifier(const char *identifier) +{ + 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 */ -void -transport_link(th_transport_t *t, th_channel_t *ch, int source_type) -{ - transport_set_channel(t, ch); - transport_init(t, source_type); - LIST_INSERT_HEAD(&all_transports, t, tht_global_link); -} - - - th_stream_t * transport_add_stream(th_transport_t *t, int pid, tv_streamtype_t type) { @@ -390,157 +466,59 @@ transport_add_stream(th_transport_t *t, int pid, tv_streamtype_t type) /** * */ -static int -transportcmp(th_transport_t *a, th_transport_t *b) +void +transport_set_channel(th_transport_t *t, const char *chname) { - return a->tht_prio - b->tht_prio; -} + th_channel_t *ch = channel_find(chname, 1, NULL); + avgstat_init(&t->tht_cc_errors, 3600); + avgstat_init(&t->tht_rate, 10); -/** - * - */ -int -transport_set_channel(th_transport_t *t, th_channel_t *ch) -{ - 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); - - config_free0(&cl); - + 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) + +static struct strtab stypetab[] = { + { "SDTV", ST_SDTV }, + { "Radio", ST_RADIO }, + { "HDTV", ST_HDTV }, + { "SDTV-AC", ST_AC_SDTV }, + { "HDTV-AC", ST_AC_HDTV }, +}; + +const char * +transport_servicetype_txt(th_transport_t *t) { - 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); + return val2str(t->tht_servicetype, stypetab); } - /** - * 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) +int +transport_is_tv(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 + t->tht_servicetype == ST_SDTV || + t->tht_servicetype == ST_HDTV || + t->tht_servicetype == ST_AC_SDTV || + t->tht_servicetype == ST_AC_HDTV; } - - -/** - * Write out a config file for a channel - */ -void -transport_settings_write(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); -} - diff --git a/transports.h b/transports.h index 3d3f4ea9..64e0a496 100644 --- a/transports.h +++ b/transports.h @@ -25,11 +25,14 @@ unsigned int transport_compute_weight(struct th_transport_list *head); 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); @@ -38,8 +41,10 @@ th_stream_t *transport_add_stream(th_transport_t *t, int pid, 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 */ diff --git a/tsdemux.c b/tsdemux.c index cfc04aaa..3563756d 100644 --- a/tsdemux.c +++ b/tsdemux.c @@ -57,13 +57,14 @@ got_section(th_transport_t *t, th_stream_t *st) { 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); + } } @@ -100,7 +101,9 @@ ts_recv_packet0(th_transport_t *t, th_stream_t *st, uint8_t *tsb) 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)); diff --git a/tvhead.h b/tvhead.h index 7534c4cf..cf023fb7 100644 --- a/tvhead.h +++ b/tvhead.h @@ -136,17 +136,16 @@ typedef struct th_v4l_adapter { * 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; @@ -171,15 +170,16 @@ typedef struct th_dvb_mux_instance { 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; /* @@ -190,10 +190,9 @@ typedef struct th_dvb_table { 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; @@ -205,20 +204,21 @@ typedef struct th_dvb_table { */ 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; + char *tda_identifier; - LIST_ENTRY(th_dvb_adapter) tda_link; - - LIST_HEAD(, th_dvb_mux) tda_muxes; - - int tda_running; + char *tda_displayname; pthread_mutex_t tda_lock; pthread_cond_t tda_cond; @@ -365,7 +365,7 @@ typedef struct th_transport { const char *tht_name; - LIST_ENTRY(th_transport) tht_global_link; + LIST_ENTRY(th_transport) tht_hash_link; enum { TRANSPORT_DVB, @@ -385,7 +385,6 @@ typedef struct th_transport { 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; @@ -393,8 +392,9 @@ typedef struct th_transport { 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; @@ -406,7 +406,11 @@ typedef struct th_transport { 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; @@ -418,6 +422,7 @@ typedef struct th_transport { void (*tht_stop_feed)(struct th_transport *t); + void (*tht_config_change)(struct th_transport *t); struct th_muxer_list tht_muxers; /* muxers */ @@ -460,9 +465,20 @@ typedef struct th_transport { } 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, @@ -866,4 +882,13 @@ extern th_channel_group_t *defgroup; 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 */ diff --git a/v4l.c b/v4l.c index 0678aded..0650df92 100644 --- a/v4l.c +++ b/v4l.c @@ -95,10 +95,10 @@ v4l_configure_transport(th_transport_t *t, const char *muxname, 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; } @@ -199,7 +199,7 @@ v4l_stop_feed(th_transport_t *t) 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; @@ -268,7 +268,7 @@ v4l_start_feed(th_transport_t *t, unsigned int weight, int status) 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;