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 00000000..a4494940 Binary files /dev/null and b/ajaxui/images/mapped.png differ diff --git a/ajaxui/images/sbbody_l.gif b/ajaxui/images/sbbody_l.gif index 12806b12..a7adf2d3 100644 Binary files a/ajaxui/images/sbbody_l.gif and b/ajaxui/images/sbbody_l.gif differ diff --git a/ajaxui/images/sbbody_r.gif b/ajaxui/images/sbbody_r.gif index 2e6cac7d..51b09101 100644 Binary files a/ajaxui/images/sbbody_r.gif and b/ajaxui/images/sbbody_r.gif differ diff --git a/ajaxui/images/sbhead_r.gif b/ajaxui/images/sbhead_r.gif index 9d7d4a1e..c6eb7fdb 100644 Binary files a/ajaxui/images/sbhead_r.gif and b/ajaxui/images/sbhead_r.gif differ diff --git a/ajaxui/images/unmapped.png b/ajaxui/images/unmapped.png new file mode 100644 index 00000000..0f2f7388 Binary files /dev/null and b/ajaxui/images/unmapped.png differ diff --git a/ajaxui/prototype/prototype.js b/ajaxui/prototype/prototype.js index e02e94da..33ec9d9e 100644 --- a/ajaxui/prototype/prototype.js +++ b/ajaxui/prototype/prototype.js @@ -1442,6 +1442,7 @@ Ajax.Updater = Class.create(Ajax.Request, { options = this.options; if (!options.evalScripts) responseText = responseText.stripScripts(); + if (!$(receiver)) this.stop(); if (receiver = $(receiver)) { if (options.insertion) { diff --git a/ajaxui/tvheadend.js b/ajaxui/tvheadend.js index eeb46862..306c5f7e 100644 --- a/ajaxui/tvheadend.js +++ b/ajaxui/tvheadend.js @@ -1,14 +1,10 @@ function switchtab(name, index) { a = new Ajax.Updater(name + 'deck', '/ajax/' + name + 'tab/' + index, - { asynchronous: true, - method: 'get', - evalScripts: true}); + { method: 'get', evalScripts: true}); a = new Ajax.Updater(name + 'menu', '/ajax/' + name + 'menu/' + index, - { asynchronous: true, - method: 'get', - evalScripts: true}); + { method: 'get', evalScripts: true}); }; function updatelistonserver(listid, url, resultid) @@ -16,8 +12,7 @@ function updatelistonserver(listid, url, resultid) // document.getElementById(resultid).innerHTML = "Updating..."; a = new Ajax.Updater(resultid, url, - { asynchronous: true, - evalScripts: true, + { evalScripts: true, parameters:Sortable.serialize(listid)}); }; @@ -27,8 +22,7 @@ function addlistentry(listid, url, name) alert("Emtpy name is not allowed"); } else { a = new Ajax.Updater(listid, url, - { asynchronous: true, - evalScripts: true, + { evalScripts: true, parameters: { name: name }, insertion: Insertion.Bottom }); @@ -48,4 +42,27 @@ function addlistentry_by_widget(listid, url, widget) name = $F(widget); $(widget).clear(); addlistentry(listid, url, name); -} \ No newline at end of file +} + + +function showhide(name) +{ + ctrlname = 'toggle_' + name; + if(document.getElementById(ctrlname).innerHTML == 'More') { + document.getElementById(ctrlname).innerHTML = 'Less'; + new Effect.Appear(name, {duration: 0.5}); + } else { + document.getElementById(ctrlname).innerHTML = 'More'; + new Effect.Fade(name, {duration: 0.5}); + } +} + +function tentative_chname(id, url, name) +{ + var newname = prompt("Enter name of channel", name); + if(newname != null && newname != name) { + a = new Ajax.Updater(id, url, + { evalScripts: true, + parameters: { newname: newname }}); + } +} diff --git a/avgen.c b/avgen.c index 98005d04..e9879e9c 100644 --- a/avgen.c +++ b/avgen.c @@ -101,7 +101,6 @@ avgen_init(void) ch = channel_find("Test PAL", 1, channel_group_find("Test channels", 1)); t = calloc(1, sizeof(th_transport_t)); - t->tht_prio = 100; t->tht_type = TRANSPORT_AVGEN; t->tht_start_feed = avgen_start_feed; @@ -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 3c47985c..1b7c724e 100644 Binary files a/dvb.c and b/dvb.c differ 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;