Work in progress on AJAX interface for tvheadend

This commit is contained in:
Andreas Öman 2008-04-07 15:57:20 +00:00
parent 827120cb75
commit d1f24092a2
39 changed files with 2234 additions and 868 deletions

View file

@ -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

View file

@ -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, "<a href=\"javascript:void(0)\" "
"onClick=\"javascript:%s\">%s</a>%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,
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\r\n"
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
/*
"<!DOCTYPE html "
"PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\r\n"
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
*/
"\r\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" "
"xml:lang=\"en\" lang=\"en\">"
@ -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();
}

View file

@ -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
*/

View file

@ -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_ */

View file

@ -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, "<li id=\"chgrp_%d\">", tcg->tcg_tag);
ajax_box_begin(tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
tcp_qprintf(tq,
"<div %s>%s</div>",
tcg == defgroup ? "" : "style=\"float: left\" ",
tcg->tcg_name);
if(tcg != defgroup) {
tcp_qprintf(tq,
"<div class=\"chgroupaction\">"
"<a href=\"javascript:void(0)\" "
"onClick=\"dellistentry('/ajax/chgroup_del','%d', '%s');\""
">Delete</a></div>",
tcg->tcg_tag, tcg->tcg_name);
}
ajax_box_end(tq, AJAX_BOX_BORDER);
tcp_qprintf(tq, "</li>");
}
/**
* Update order of channel groups
*/
static int
ajax_chgroup_updateorder(http_connection_t *hc, const char *remain,
void *opaque)
{
th_channel_group_t *tcg;
tcp_queue_t tq;
http_arg_t *ra;
tcp_init_queue(&tq, -1);
TAILQ_FOREACH(ra, &hc->hc_req_args, link) {
if(strcmp(ra->key, "channelgrouplist[]") ||
(tcg = channel_group_by_tag(atoi(ra->val))) == NULL)
continue;
TAILQ_REMOVE(&all_channel_groups, tcg, tcg_global_link);
TAILQ_INSERT_TAIL(&all_channel_groups, tcg, tcg_global_link);
}
tcp_qprintf(&tq, "<span id=\"updatedok\">Updated on server</span>");
ajax_js(&tq, "Effect.Fade('updatedok')");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Add a new channel group
*/
static int
ajax_chgroup_add(http_connection_t *hc, const char *remain, void *opaque)
{
th_channel_group_t *tcg;
tcp_queue_t tq;
const char *name;
tcp_init_queue(&tq, -1);
if((name = http_arg_get(&hc->hc_req_args, "name")) != NULL) {
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link)
if(!strcmp(name, tcg->tcg_name))
break;
if(tcg == NULL) {
tcg = channel_group_find(name, 1);
ajax_chgroup_build(&tq, tcg);
/* We must recreate the Sortable object */
ajax_js(&tq, "Sortable.destroy(\"channelgrouplist\")");
ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
"{onUpdate:function(){updatelistonserver("
"'channelgrouplist', "
"'/ajax/chgroup_updateorder', "
"'list-info'"
")}});");
}
}
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Delete a channel group
*/
static int
ajax_chgroup_del(http_connection_t *hc, const char *remain, void *opaque)
{
th_channel_group_t *tcg;
tcp_queue_t tq;
const char *id;
if((id = http_arg_get(&hc->hc_req_args, "id")) == NULL)
return HTTP_STATUS_BAD_REQUEST;
if((tcg = channel_group_by_tag(atoi(id))) == NULL)
return HTTP_STATUS_BAD_REQUEST;
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq, "$('chgrp_%d').remove();", tcg->tcg_tag);
http_output_queue(hc, &tq, "text/javascript; charset=UTF-8", 0);
channel_group_destroy(tcg);
return 0;
}
/**
* Channel group & channel configuration
*/
static int
ajax_channel_config_tab(http_connection_t *hc)
{
tcp_queue_t tq;
th_channel_group_t *tcg;
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq, "<div style=\"float: left; width: 400px\">");
ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, "channelgroups",
NULL, "Channel groups");
tcp_qprintf(&tq, "<div style=\"height:15px; text-align:center\" "
"id=\"list-info\"></div>");
tcp_qprintf(&tq, "<ul id=\"channelgrouplist\" class=\"draglist\">");
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
ajax_chgroup_build(&tq, tcg);
}
tcp_qprintf(&tq, "</ul>");
ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
"{onUpdate:function(){updatelistonserver("
"'channelgrouplist', "
"'/ajax/chgroup_updateorder', "
"'list-info'"
")}});");
/**
* Add new group
*/
ajax_box_begin(&tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
tcp_qprintf(&tq,
"<div style=\"height: 20px\">"
"<div style=\"float: left\">"
"<input class=\"textinput\" type=\"text\" id=\"newchgrp\">"
"</div>"
"<div style=\"padding-top:2px\" class=\"chgroupaction\">"
"<a href=\"javascript:void(0)\" "
"onClick=\"javascript:addlistentry_by_widget("
"'channelgrouplist', 'chgroup_add', 'newchgrp');\">"
"Add</a></div>"
"</div>");
ajax_box_end(&tq, AJAX_BOX_BORDER);
ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
tcp_qprintf(&tq, "</div>");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/*
* Tab AJAX page
*
@ -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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "tvhead.h"
#include "http.h"
#include "ajaxui.h"
#include "channels.h"
/**
* Render a channel group widget
*/
static void
ajax_chgroup_build(tcp_queue_t *tq, th_channel_group_t *tcg)
{
tcp_qprintf(tq, "<li id=\"chgrp_%d\">", tcg->tcg_tag);
ajax_box_begin(tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
tcp_qprintf(tq,
"<div %s>%s</div>",
tcg == defgroup ? "" : "style=\"float: left\" ",
tcg->tcg_name);
if(tcg != defgroup) {
tcp_qprintf(tq,
"<div class=\"chgroupaction\">"
"<a href=\"javascript:void(0)\" "
"onClick=\"dellistentry('/ajax/chgroup_del','%d', '%s');\""
">Delete</a></div>",
tcg->tcg_tag, tcg->tcg_name);
}
ajax_box_end(tq, AJAX_BOX_BORDER);
tcp_qprintf(tq, "</li>");
}
/**
* Update order of channel groups
*/
static int
ajax_chgroup_updateorder(http_connection_t *hc, const char *remain,
void *opaque)
{
th_channel_group_t *tcg;
tcp_queue_t tq;
http_arg_t *ra;
tcp_init_queue(&tq, -1);
TAILQ_FOREACH(ra, &hc->hc_req_args, link) {
if(strcmp(ra->key, "channelgrouplist[]") ||
(tcg = channel_group_by_tag(atoi(ra->val))) == NULL)
continue;
TAILQ_REMOVE(&all_channel_groups, tcg, tcg_global_link);
TAILQ_INSERT_TAIL(&all_channel_groups, tcg, tcg_global_link);
}
tcp_qprintf(&tq, "<span id=\"updatedok\">Updated on server</span>");
ajax_js(&tq, "Effect.Fade('updatedok')");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Add a new channel group
*/
static int
ajax_chgroup_add(http_connection_t *hc, const char *remain, void *opaque)
{
th_channel_group_t *tcg;
tcp_queue_t tq;
const char *name;
tcp_init_queue(&tq, -1);
if((name = http_arg_get(&hc->hc_req_args, "name")) != NULL) {
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link)
if(!strcmp(name, tcg->tcg_name))
break;
if(tcg == NULL) {
tcg = channel_group_find(name, 1);
ajax_chgroup_build(&tq, tcg);
/* We must recreate the Sortable object */
ajax_js(&tq, "Sortable.destroy(\"channelgrouplist\")");
ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
"{onUpdate:function(){updatelistonserver("
"'channelgrouplist', "
"'/ajax/chgroup_updateorder', "
"'list-info'"
")}});");
}
}
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Delete a channel group
*/
static int
ajax_chgroup_del(http_connection_t *hc, const char *remain, void *opaque)
{
th_channel_group_t *tcg;
tcp_queue_t tq;
const char *id;
if((id = http_arg_get(&hc->hc_req_args, "id")) == NULL)
return HTTP_STATUS_BAD_REQUEST;
if((tcg = channel_group_by_tag(atoi(id))) == NULL)
return HTTP_STATUS_BAD_REQUEST;
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq, "$('chgrp_%d').remove();", tcg->tcg_tag);
http_output_queue(hc, &tq, "text/javascript; charset=UTF-8", 0);
channel_group_destroy(tcg);
return 0;
}
/**
* Channel group & channel configuration
*/
int
ajax_config_channels_tab(http_connection_t *hc)
{
tcp_queue_t tq;
th_channel_group_t *tcg;
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq, "<div style=\"float: left; width: 400px\">");
ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, "channelgroups",
NULL, "Channel groups");
tcp_qprintf(&tq, "<div style=\"height:15px; text-align:center\" "
"id=\"list-info\"></div>");
tcp_qprintf(&tq, "<ul id=\"channelgrouplist\" class=\"draglist\">");
TAILQ_FOREACH(tcg, &all_channel_groups, tcg_global_link) {
if(tcg->tcg_hidden)
continue;
ajax_chgroup_build(&tq, tcg);
}
tcp_qprintf(&tq, "</ul>");
ajax_js(&tq, "Sortable.create(\"channelgrouplist\", "
"{onUpdate:function(){updatelistonserver("
"'channelgrouplist', "
"'/ajax/chgroup_updateorder', "
"'list-info'"
")}});");
/**
* Add new group
*/
ajax_box_begin(&tq, AJAX_BOX_BORDER, NULL, NULL, NULL);
tcp_qprintf(&tq,
"<div style=\"height: 20px\">"
"<div style=\"float: left\">"
"<input class=\"textinput\" type=\"text\" id=\"newchgrp\">"
"</div>"
"<div style=\"padding-top:2px\" class=\"chgroupaction\">"
"<a href=\"javascript:void(0)\" "
"onClick=\"javascript:addlistentry_by_widget("
"'channelgrouplist', 'chgroup_add', 'newchgrp');\">"
"Add</a></div>"
"</div>");
ajax_box_end(&tq, AJAX_BOX_BORDER);
ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
tcp_qprintf(&tq, "</div>");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
*
*/
void
ajax_config_channels_init(void)
{
http_path_add("/ajax/chgroup_add" , NULL, ajax_chgroup_add);
http_path_add("/ajax/chgroup_del" , NULL, ajax_chgroup_del);
http_path_add("/ajax/chgroup_updateorder", NULL, ajax_chgroup_updateorder);
}

662
ajaxui/ajaxui_config_dvb.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/dvb/frontend.h>
#include "tvhead.h"
#include "http.h"
#include "ajaxui.h"
#include "channels.h"
#include "dvb.h"
#include "dvb_support.h"
#include "dvb_muxconfig.h"
#include "strtab.h"
#include "psi.h"
#include "transports.h"
static struct strtab adapterstatus[] = {
{ "Unconfigured", TDA_STATE_UNCONFIGURED },
{ "Running", TDA_STATE_RUNNING },
{ "Zombie", TDA_STATE_ZOMBIE },
};
static void
add_option(tcp_queue_t *tq, int bol, const char *name)
{
if(bol)
tcp_qprintf(tq, "<option>%s</option>", name);
}
static void
tdmi_displayname(th_dvb_mux_instance_t *tdmi, char *buf, size_t len)
{
int f = tdmi->tdmi_fe_params->frequency;
if(tdmi->tdmi_adapter->tda_fe_info->type == FE_QPSK) {
snprintf(buf, len, "%d kHz %s", f,
dvb_polarisation_to_str(tdmi->tdmi_polarisation));
} else {
snprintf(buf, len, "%d kHz", f / 1000);
}
}
/*
* Adapter summary
*/
static int
ajax_adaptersummary(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_dvb_adapter_t *tda;
char dispname[20];
if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
tcp_init_queue(&tq, -1);
snprintf(dispname, sizeof(dispname), "%s", tda->tda_displayname);
strcpy(dispname + sizeof(dispname) - 4, "...");
ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, NULL, NULL, dispname);
tcp_qprintf(&tq, "<div class=\"infoprefix\">Status:</div>"
"<div>%s</div>",
val2str(tda->tda_state, adapterstatus) ?: "invalid");
tcp_qprintf(&tq, "<div class=\"infoprefix\">Type:</div>"
"<div>%s</div>",
dvb_adaptertype_to_str(tda->tda_fe_info->type));
tcp_qprintf(&tq, "<div class=\"infoprefix\">Tuner:</div>"
"<div>...</div>");
tcp_qprintf(&tq, "<div style=\"text-align: center\">"
"<a href=\"javascript:void(0);\" "
"onClick=\"new Ajax.Updater('dvbadaptereditor', "
"'/ajax/dvbadaptereditor/%s', "
"{method: 'get', evalScripts: true})\""
"\">Edit</a></div>", tda->tda_identifier);
ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* DVB configuration
*/
int
ajax_config_dvb_tab(http_connection_t *hc)
{
tcp_queue_t tq;
th_dvb_adapter_t *tda;
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq, "<div style=\"overflow: auto; width: 100%\">");
LIST_FOREACH(tda, &dvb_adapters, tda_global_link) {
tcp_qprintf(&tq, "<div id=\"summary_%s\" "
"style=\"float:left; width: 250px\"></div>",
tda->tda_identifier);
ajax_js(&tq, "new Ajax.Updater('summary_%s', "
"'/ajax/dvbadaptersummary/%s', {method: 'get'})",
tda->tda_identifier, tda->tda_identifier);
}
tcp_qprintf(&tq, "</div>");
tcp_qprintf(&tq, "<div id=\"dvbadaptereditor\"></div>");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Generate the 'add new...' mux link
*
* if result is set we add a fade out of a result from a previous op
*/
static void
dvb_make_add_link(tcp_queue_t *tq, th_dvb_adapter_t *tda, const char *result)
{
tcp_qprintf(tq,
"<p><a href=\"javascript:void(0);\" "
"onClick=\"new Ajax.Updater('addmux', "
"'/ajax/dvbadapteraddmux/%s', {method: 'get'})\""
">Add new...</a></p>", tda->tda_identifier);
if(result) {
tcp_qprintf(tq, "<div id=\"result\">%s</div>", result);
ajax_js(tq, "Effect.Fade('result')");
}
}
/**
* DVB adapter editor pane
*/
static int
ajax_adaptereditor(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_dvb_adapter_t *tda;
float a, b, c;
if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
tcp_init_queue(&tq, -1);
ajax_box_begin(&tq, AJAX_BOX_FILLED, NULL, NULL, NULL);
tcp_qprintf(&tq,
"<div style=\"text-align: center; font-weight: bold\">%s</div>",
tda->tda_displayname);
ajax_box_end(&tq, AJAX_BOX_FILLED);
/* Type */
tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Model:</div>"
"<div>%s (%s)</div>",
tda->tda_fe_info->name,
dvb_adaptertype_to_str(tda->tda_fe_info->type));
switch(tda->tda_fe_info->type) {
case FE_QPSK:
a = tda->tda_fe_info->frequency_min;
b = tda->tda_fe_info->frequency_max;
c = tda->tda_fe_info->frequency_stepsize;
break;
default:
a = tda->tda_fe_info->frequency_min / 1000.0f;
b = tda->tda_fe_info->frequency_max / 1000.0f;
c = tda->tda_fe_info->frequency_stepsize / 1000.0f;
break;
}
tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Freq. Range:</div>"
"<div>%.2f - %.2f kHz, in steps of %.2f kHz</div>",
a, b, c);
if(tda->tda_fe_info->symbol_rate_min) {
tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Symbolrate:</div>"
"<div>%d - %d BAUD</div>",
tda->tda_fe_info->symbol_rate_min,
tda->tda_fe_info->symbol_rate_max);
}
/* Capabilities */
// tcp_qprintf(&tq, "<div class=\"infoprefixwide\">Capabilities:</div>");
tcp_qprintf(&tq, "<div style=\"float: left; width:45%\">");
ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, NULL, NULL, "Multiplexes");
/* List of muxes */
tcp_qprintf(&tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(&tq, "<div style=\"float: left; width: 20%\">"
"Freq.</div>");
tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
"Status");
tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%s</div>",
"State");
tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
"Name");
tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%s</div>",
"Services");
tcp_qprintf(&tq, "</div><hr>");
tcp_qprintf(&tq, "<div id=\"dvbmuxlist%s\" class=\"normallist\">",
tda->tda_identifier);
tcp_qprintf(&tq, "</div>");
ajax_js(&tq,
"new Ajax.PeriodicalUpdater('dvbmuxlist%s', "
"'/ajax/dvbadaptermuxlist/%s', {method: 'get', frequency: 5}) ",
tda->tda_identifier, tda->tda_identifier);
tcp_qprintf(&tq, "<hr><div id=\"addmux\">");
dvb_make_add_link(&tq, tda, NULL);
tcp_qprintf(&tq, "</div>");
ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
tcp_qprintf(&tq, "</div>");
/* Div for displaying services */
tcp_qprintf(&tq, "<div id=\"servicepane\" "
"style=\"float: left; width:55%\">");
tcp_qprintf(&tq, "</div>");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* DVB adapter add mux
*/
static int
ajax_adapteraddmux(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_dvb_adapter_t *tda;
int caps;
int fetype;
char params[400];
if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
caps = tda->tda_fe_info->caps;
fetype = tda->tda_fe_info->type;
tcp_init_queue(&tq, -1);
tcp_qprintf(&tq, "<div style=\"text-align: center; font-weight: bold\">"
"Add new %s mux</div>",
dvb_adaptertype_to_str(tda->tda_fe_info->type));
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">Frequency (%s):</div>"
"<div>"
"<input class=\"textinput\" type=\"text\" id=\"freq\">"
"</div>",
fetype == FE_QPSK ? "kHz" : "Hz");
snprintf(params, sizeof(params),
"freq: $F('freq')");
/* Symbolrate */
if(fetype == FE_QAM || fetype == FE_QPSK) {
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">Symbolrate:</div>"
"<div>"
"<input class=\"textinput\" type=\"text\" id=\"symrate\">"
"</div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", symrate: $F('symrate')");
}
/* Bandwidth */
if(fetype == FE_OFDM) {
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">Bandwidth:</div>"
"<div><select id=\"bw\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_BANDWIDTH_AUTO, "AUTO");
add_option(&tq, 1 , "8MHz");
add_option(&tq, 1 , "7MHz");
add_option(&tq, 1 , "6MHz");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", bw: $F('bw')");
}
/* Constellation */
if(fetype == FE_QAM || fetype == FE_OFDM) {
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">Constellation:</div>"
"<div><select id=\"const\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_QAM_AUTO, "AUTO");
add_option(&tq, caps & FE_CAN_QPSK, "QPSK");
add_option(&tq, caps & FE_CAN_QAM_16, "QAM16");
add_option(&tq, caps & FE_CAN_QAM_32, "QAM32");
add_option(&tq, caps & FE_CAN_QAM_64, "QAM64");
add_option(&tq, caps & FE_CAN_QAM_128, "QAM128");
add_option(&tq, caps & FE_CAN_QAM_256, "QAM256");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", const: $F('const')");
}
/* FEC */
if(fetype == FE_QAM || fetype == FE_QPSK) {
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">FEC:</div>"
"<div><select id=\"fec\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_FEC_AUTO, "AUTO");
add_option(&tq, caps & FE_CAN_FEC_1_2, "1/2");
add_option(&tq, caps & FE_CAN_FEC_2_3, "2/3");
add_option(&tq, caps & FE_CAN_FEC_3_4, "3/4");
add_option(&tq, caps & FE_CAN_FEC_4_5, "4/5");
add_option(&tq, caps & FE_CAN_FEC_5_6, "5/6");
add_option(&tq, caps & FE_CAN_FEC_6_7, "6/7");
add_option(&tq, caps & FE_CAN_FEC_7_8, "7/8");
add_option(&tq, caps & FE_CAN_FEC_8_9, "8/9");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", fec: $F('fec')");
}
if(fetype == FE_OFDM) {
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">Transmission mode:</div>"
"<div><select id=\"tmode\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_TRANSMISSION_MODE_AUTO, "AUTO");
add_option(&tq, 1 , "2k");
add_option(&tq, 1 , "8k");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", tmode: $F('tmode')");
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">Guard interval:</div>"
"<div><select id=\"guard\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_GUARD_INTERVAL_AUTO, "AUTO");
add_option(&tq, 1 , "1/32");
add_option(&tq, 1 , "1/16");
add_option(&tq, 1 , "1/8");
add_option(&tq, 1 , "1/4");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", guard: $F('guard')");
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">Hierarchy:</div>"
"<div><select id=\"hier\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_HIERARCHY_AUTO, "AUTO");
add_option(&tq, 1 , "1");
add_option(&tq, 1 , "2");
add_option(&tq, 1 , "4");
add_option(&tq, 1 , "NONE");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", hier: $F('hier')");
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">FEC Hi:</div>"
"<div><select id=\"fechi\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_FEC_AUTO, "AUTO");
add_option(&tq, caps & FE_CAN_FEC_1_2, "1/2");
add_option(&tq, caps & FE_CAN_FEC_2_3, "2/3");
add_option(&tq, caps & FE_CAN_FEC_3_4, "3/4");
add_option(&tq, caps & FE_CAN_FEC_4_5, "4/5");
add_option(&tq, caps & FE_CAN_FEC_5_6, "5/6");
add_option(&tq, caps & FE_CAN_FEC_6_7, "6/7");
add_option(&tq, caps & FE_CAN_FEC_7_8, "7/8");
add_option(&tq, caps & FE_CAN_FEC_8_9, "8/9");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", fechi: $F('fechi')");
tcp_qprintf(&tq,
"<div class=\"infoprefixwidefat\">FEC Low:</div>"
"<div><select id=\"feclo\" class=\"textinput\">");
add_option(&tq, caps & FE_CAN_FEC_AUTO, "AUTO");
add_option(&tq, caps & FE_CAN_FEC_1_2, "1/2");
add_option(&tq, caps & FE_CAN_FEC_2_3, "2/3");
add_option(&tq, caps & FE_CAN_FEC_3_4, "3/4");
add_option(&tq, caps & FE_CAN_FEC_4_5, "4/5");
add_option(&tq, caps & FE_CAN_FEC_5_6, "5/6");
add_option(&tq, caps & FE_CAN_FEC_6_7, "6/7");
add_option(&tq, caps & FE_CAN_FEC_7_8, "7/8");
add_option(&tq, caps & FE_CAN_FEC_8_9, "8/9");
tcp_qprintf(&tq, "</select></div>");
snprintf(params + strlen(params), sizeof(params) - strlen(params),
", feclo: $F('feclo')");
}
tcp_qprintf(&tq,
"<div style=\"text-align: center\">"
"<input type=\"button\" value=\"Create\" "
"onClick=\"new Ajax.Updater('addmux', "
"'/ajax/dvbadaptercreatemux/%s', "
"{evalScripts: true, parameters: {%s}})"
"\">"
"</div>", tda->tda_identifier, params);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
*
*/
static int
ajax_adaptercreatemux_fail(http_connection_t *hc, th_dvb_adapter_t *tda,
const char *errmsg)
{
tcp_queue_t tq;
tcp_init_queue(&tq, -1);
dvb_make_add_link(&tq, tda, errmsg);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* DVB adapter create mux (come here on POST after addmux query)
*/
static int
ajax_adaptercreatemux(http_connection_t *hc, const char *remain, void *opaque)
{
tcp_queue_t tq;
th_dvb_adapter_t *tda;
const char *v;
if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
v = dvb_mux_create_str(tda,
http_arg_get(&hc->hc_req_args, "freq"),
http_arg_get(&hc->hc_req_args, "symrate"),
http_arg_get(&hc->hc_req_args, "const"),
http_arg_get(&hc->hc_req_args, "fec"),
http_arg_get(&hc->hc_req_args, "fechi"),
http_arg_get(&hc->hc_req_args, "feclo"),
http_arg_get(&hc->hc_req_args, "bw"),
http_arg_get(&hc->hc_req_args, "tmode"),
http_arg_get(&hc->hc_req_args, "guard"),
http_arg_get(&hc->hc_req_args, "hier"),
http_arg_get(&hc->hc_req_args, "pol"),
http_arg_get(&hc->hc_req_args, "port"), 1);
if(v != NULL)
return ajax_adaptercreatemux_fail(hc, tda, v);
tcp_init_queue(&tq, -1);
dvb_make_add_link(&tq, tda, "Successfully created");
ajax_js(&tq,
"new Ajax.Updater('dvbmuxlist%s', "
"'/ajax/dvbadaptermuxlist/%s', {method: 'get'}) ",
tda->tda_identifier, tda->tda_identifier);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Return a list of all muxes on the given adapter
*/
static int
ajax_adaptermuxlist(http_connection_t *hc, const char *remain, void *opaque)
{
th_dvb_mux_instance_t *tdmi;
tcp_queue_t tq;
th_dvb_adapter_t *tda;
char buf[50];
const char *txt;
int fetype, n;
th_transport_t *t;
int o = 1;
if(remain == NULL || (tda = dvb_adapter_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
fetype = tda->tda_fe_info->type;
tcp_init_queue(&tq, -1);
if(LIST_FIRST(&tda->tda_muxes) == NULL) {
tcp_qprintf(&tq, "<div style=\"text-align: center\">"
"No muxes configured</div>");
} else LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
tcp_qprintf(&tq, "<div%s>", o ? " style=\"background: #fff\"" : "");
o = !o;
tdmi_displayname(tdmi, buf, sizeof(buf));
tcp_qprintf(&tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(&tq, "<div style=\"float: left; width: 20%\">"
"<a href=\"javascript:void(0);\" "
"onClick=\"new Ajax.Updater('servicepane', "
"'/ajax/dvbmuxeditor/%s', "
"{method: 'get', evalScripts: true})\""
">%s</a></div>", tdmi->tdmi_identifier, buf);
tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
dvb_mux_status(tdmi));
switch(tdmi->tdmi_state) {
case TDMI_IDLE: txt = "Idle"; break;
case TDMI_IDLESCAN: txt = "Scanning"; break;
case TDMI_RUNNING: txt = "Running"; break;
default: txt = "???"; break;
}
tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%s</div>",
txt);
txt = tdmi->tdmi_network;
if(txt == NULL)
txt = "Unknown";
tcp_qprintf(&tq, "<div style=\"float: left; width: 25%\">%s</div>",
txt);
n = 0;
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link)
n++;
tcp_qprintf(&tq, "<div style=\"float: left; width: 15%\">%d</div>", n);
tcp_qprintf(&tq, "</div></div>");
}
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
* Display detailes about a mux
*/
static int
ajax_dvbmuxeditor(http_connection_t *hc, const char *remain, void *opaque)
{
th_dvb_mux_instance_t *tdmi;
tcp_queue_t tq;
char buf[1000];
th_transport_t *t;
struct th_transport_list head;
if(remain == NULL || (tdmi = dvb_mux_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
tcp_init_queue(&tq, -1);
tdmi_displayname(tdmi, buf, sizeof(buf));
LIST_INIT(&head);
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_mux_link) {
if(transport_servicetype_txt(t) == NULL)
continue;
LIST_INSERT_HEAD(&head, t, tht_tmp_link);
}
ajax_box_begin(&tq, AJAX_BOX_SIDEBOX, NULL, NULL, buf);
ajax_transport_build_list(&tq, &head);
ajax_box_end(&tq, AJAX_BOX_SIDEBOX);
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
return 0;
}
/**
*
*/
void
ajax_config_dvb_init(void)
{
http_path_add("/ajax/dvbadaptermuxlist" , NULL, ajax_adaptermuxlist);
http_path_add("/ajax/dvbadaptersummary" , NULL, ajax_adaptersummary);
http_path_add("/ajax/dvbadaptereditor", NULL, ajax_adaptereditor);
http_path_add("/ajax/dvbadapteraddmux", NULL, ajax_adapteraddmux);
http_path_add("/ajax/dvbadaptercreatemux", NULL, ajax_adaptercreatemux);
http_path_add("/ajax/dvbmuxeditor", NULL, ajax_dvbmuxeditor);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/dvb/frontend.h>
#include "tvhead.h"
#include "http.h"
#include "ajaxui.h"
#include "channels.h"
#include "strtab.h"
#include "psi.h"
#include "transports.h"
/**
*
*/
int
ajax_transport_build_list(tcp_queue_t *tq, struct th_transport_list *tlist)
{
char buf[1000];
th_transport_t *t;
const char *v;
int o = 1;
th_stream_t *st;
const char *extra;
tcp_qprintf(tq, "<script type=\"text/javascript\">\r\n"
"//<![CDATA[\r\n");
/* Select all */
tcp_qprintf(tq, "select_all = function() {\r\n");
LIST_FOREACH(t, tlist, tht_tmp_link) {
tcp_qprintf(tq,
"document.getElementById('sel_%s').checked = true;\r\n",
t->tht_identifier);
}
tcp_qprintf(tq, "}\r\n");
/* Select none */
tcp_qprintf(tq, "select_none = function() {\r\n");
LIST_FOREACH(t, tlist, tht_tmp_link) {
tcp_qprintf(tq,
"document.getElementById('sel_%s').checked = false;\r\n",
t->tht_identifier);
}
tcp_qprintf(tq, "}\r\n");
/* Invert selection */
tcp_qprintf(tq, "select_invert = function() {\r\n");
LIST_FOREACH(t, tlist, tht_tmp_link) {
tcp_qprintf(tq,
"document.getElementById('sel_%s').checked = !"
"document.getElementById('sel_%s').checked;\r\n",
t->tht_identifier, t->tht_identifier);
}
tcp_qprintf(tq, "}\r\n");
/* Select TV transports */
tcp_qprintf(tq, "select_tv = function() {\r\n");
LIST_FOREACH(t, tlist, tht_tmp_link) {
tcp_qprintf(tq,
"document.getElementById('sel_%s').checked = %s;\r\n",
t->tht_identifier,
transport_is_tv(t) ? "true" : "false");
}
tcp_qprintf(tq, "}\r\n");
/* Select unscrambled TV transports */
tcp_qprintf(tq, "select_tv_nocrypt = function() {\r\n");
LIST_FOREACH(t, tlist, tht_tmp_link) {
tcp_qprintf(tq,
"document.getElementById('sel_%s').checked = %s;\r\n",
t->tht_identifier,
transport_is_tv(t) && !t->tht_scrambled ? "true" : "false");
}
tcp_qprintf(tq, "}\r\n");
/* Perform the given op on all transprots */
tcp_qprintf(tq, "selected_do = function(op) {\r\n");
LIST_FOREACH(t, tlist, tht_tmp_link) {
tcp_qprintf(tq,
"if(document.getElementById('sel_%s').checked) {\r\n"
" new Ajax.Request('/ajax/transport_op/%s', "
"{parameters: {action: op}});\r\n}\r\n",
t->tht_identifier, t->tht_identifier);
}
tcp_qprintf(tq, "}\r\n");
tcp_qprintf(tq,
"\r\n//]]>\r\n"
"</script>\r\n");
/* Top */
tcp_qprintf(tq, "<form id=\"transports\">");
tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">&nbsp;</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">SID</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 10%\">Crypto</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 12%\">Type</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 25%\">"
"Service</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 6%\">&nbsp;</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 25%\">"
"Channel</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 6%\">"
"Bah</div>");
tcp_qprintf(tq, "</div><hr>");
LIST_FOREACH(t, tlist, tht_tmp_link) {
tcp_qprintf(tq, "<div%s>", o ? " style=\"background: #fff\"" : "");
o = !o;
tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">"
"<a id=\"toggle_svcinfo%s\" href=\"javascript:void(0)\" "
"onClick=\"showhide('svcinfo%s')\" >"
"More</a></div>", t->tht_identifier, t->tht_identifier);
tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">%d</div>",
t->tht_dvb_service_id);
tcp_qprintf(tq, "<div style=\"float: left; width: 10%\">%s</div>",
t->tht_scrambled ? "CSA" : "Free");
tcp_qprintf(tq, "<div style=\"float: left; width: 12%\">%s</div>",
transport_servicetype_txt(t));
tcp_qprintf(tq, "<div style=\"float: left; width: 25%\">%s</div>",
t->tht_servicename ?: "");
tcp_qprintf(tq,
"<div style=\"float: left; width: 6%; text-align: center\">"
"<a href=\"javascript:void(0)\" "
"onClick=\"new "
"Ajax.Request('/ajax/transport_op/%s', "
"{parameters: {action: 'toggle'}})\">"
"<img id=\"map%s\" src=\"/gfx/unmapped.png\"></a></div>",
t->tht_identifier, t->tht_identifier);
tcp_qprintf(tq, "<div id=\"chname%s\" style=\"float: left; width: 25%\">",
t->tht_identifier);
if(t->tht_channel == NULL) {
/* Unmapped */
v = t->tht_channelname;
snprintf(buf, sizeof(buf),
"tentative_chname('chname%s', "
"'/ajax/transport_rename_channel/%s', '%s')",
t->tht_identifier, t->tht_identifier, v);
ajax_a_jsfunc(tq, v, buf, "");
} else {
/* Mapped */
tcp_qprintf(tq, "%s", t->tht_channel->ch_name);
}
tcp_qprintf(tq, "</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 6%\">"
"<input id=\"sel_%s\" type=\"checkbox\" class=\"nicebox\">"
"</div>", t->tht_identifier);
tcp_qprintf(tq, "</div>");
/* Extra info */
tcp_qprintf(tq, "<div name=\"id=\"svcinfo%s\" style=\"display: none\">",
t->tht_identifier);
tcp_qprintf(tq, "<p>");
tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">"
"&nbsp;</div>");
tcp_qprintf(tq, "<div class=\"pidheader\" style=\"width: 8%\">"
"PID</div>");
tcp_qprintf(tq, "<div class=\"pidheader\" style=\"width: 22%\">"
"Payload</div>");
tcp_qprintf(tq, "<div class=\"pidheader\" style=\"width: 62%\">Details"
"</div>");
tcp_qprintf(tq, "</div>");
LIST_FOREACH(st, &t->tht_streams, st_link) {
tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">&nbsp;</div>");
tcp_qprintf(tq, "<div style=\"float: left; width: 8%\">%d</div>",
st->st_pid);
tcp_qprintf(tq, "<div style=\"float: left; width: 22%\">%s</div>",
htstvstreamtype2txt(st->st_type));
switch(st->st_type) {
case HTSTV_MPEG2AUDIO:
case HTSTV_AC3:
extra = st->st_lang;
break;
case HTSTV_CA:
extra = psi_caid2name(st->st_caid);
break;
default:
extra = NULL;
break;
}
if(extra != NULL)
tcp_qprintf(tq, "<div style=\"float: left; width: 62%\">%s</div>",
extra);
tcp_qprintf(tq, "</div>");
}
tcp_qprintf(tq, "</p></div>");
tcp_qprintf(tq, "</div>\r\n");
}
tcp_qprintf(tq, "<hr>\r\n");
tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(tq, "<div class=\"infoprefix\">Select:</div><div>");
ajax_a_jsfunc(tq, "All", "select_all();", " / ");
ajax_a_jsfunc(tq, "None", "select_none();", " / ");
ajax_a_jsfunc(tq, "Invert", "select_invert();", " / ");
ajax_a_jsfunc(tq, "All TV-services", "select_tv();", " / ");
ajax_a_jsfunc(tq, "All uncrypted TV-services", "select_tv_nocrypt();", "");
tcp_qprintf(tq, "</div></div>\r\n");
tcp_qprintf(tq, "<div style=\"overflow: auto; width: 100%\">");
tcp_qprintf(tq, "<div class=\"infoprefix\">&nbsp;</div><div>");
ajax_a_jsfunc(tq, "Map selected", "selected_do('map');", " / ");
ajax_a_jsfunc(tq, "Unmap selected", "selected_do('unmap');", "");
tcp_qprintf(tq, "</div></div>");
tcp_qprintf(tq, "</form>");
return 0;
}
/**
* Rename of unmapped channel
*/
static int
ajax_transport_rename_channel(http_connection_t *hc,
const char *remain, void *opaque)
{
th_transport_t *t;
const char *newname, *v;
tcp_queue_t tq;
char buf[1000];
if(remain == NULL || (t = transport_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
if((newname = http_arg_get(&hc->hc_req_args, "newname")) == NULL)
return HTTP_STATUS_BAD_REQUEST;
free((void *)t->tht_channelname);
t->tht_channelname = strdup(newname);
tcp_init_queue(&tq, -1);
v = newname;
snprintf(buf, sizeof(buf),
"tentative_chname('chname%s', "
"'/ajax/transport_rename_channel/%s', '%s')",
t->tht_identifier, t->tht_identifier, v);
ajax_a_jsfunc(&tq, v, buf, "");
http_output_queue(hc, &tq, "text/html; charset=UTF-8", 0);
t->tht_config_change(t);
return 0;
}
/**
*
*/
static void
dvb_map_channel(th_transport_t *t, tcp_queue_t *tq)
{
transport_set_channel(t, t->tht_channelname);
printf("Mapped transport %s to channel %s\n",
t->tht_servicename, t->tht_channel->ch_name);
tcp_qprintf(tq,
"document.getElementById('chname%s').innerHTML='%s';\n\r"
"document.getElementById('map%s').src='/gfx/mapped.png';\n\r",
t->tht_identifier, t->tht_channel->ch_name,
t->tht_identifier);
}
/**
*
*/
static void
dvb_unmap_channel(th_transport_t *t, tcp_queue_t *tq)
{
transport_unset_channel(t);
printf("Unmapped transport %s\n", t->tht_servicename);
tcp_qprintf(tq,
"document.getElementById('chname%s').innerHTML='"
"<a href=\"javascript:void(0)\" "
"onClick=\"javascript:tentative_chname(\\'chname%s\\', "
"\\'/ajax/transport_rename_channel/%s\\', \\'%s\\')\">%s</a>"
"';\n\r"
"document.getElementById('map%s').src='/gfx/unmapped.png';\n\r",
t->tht_identifier, t->tht_identifier, t->tht_identifier,
t->tht_channelname, t->tht_channelname, t->tht_identifier);
}
/**
*
*/
int
ajax_transport_op(http_connection_t *hc, const char *remain, void *opaque)
{
th_transport_t *t;
tcp_queue_t tq;
const char *op;
if(remain == NULL || (t = transport_find_by_identifier(remain)) == NULL)
return HTTP_STATUS_NOT_FOUND;
if((op = http_arg_get(&hc->hc_req_args, "action")) == NULL)
return HTTP_STATUS_BAD_REQUEST;
tcp_init_queue(&tq, -1);
if(!strcmp(op, "toggle")) {
if(t->tht_channel)
dvb_unmap_channel(t, &tq);
else
dvb_map_channel(t, &tq);
} else if(!strcmp(op, "map") && t->tht_channel == NULL) {
dvb_map_channel(t, &tq);
} else if(!strcmp(op, "unmap") && t->tht_channel != NULL) {
dvb_unmap_channel(t, &tq);
}
http_output_queue(hc, &tq, "text/javascript; charset=UTF-8", 0);
t->tht_config_change(t);
return 0;
}
/**
*
*/
void
ajax_config_transport_init(void)
{
http_path_add("/ajax/transport_rename_channel", NULL,
ajax_transport_rename_channel);
http_path_add("/ajax/transport_op", NULL,
ajax_transport_op);
}

BIN
ajaxui/images/mapped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
ajaxui/images/unmapped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

View file

@ -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) {

View file

@ -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);
}
}
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 }});
}
}

View file

@ -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);
}

View file

@ -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

4
cwc.c
View file

@ -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);

BIN
dvb.c

Binary file not shown.

14
dvb.h
View file

@ -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_ */

View file

@ -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);

View file

@ -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 */

View file

@ -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

View file

@ -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 */

View file

@ -29,8 +29,12 @@
#include <stdlib.h>
#include <string.h>
#include <linux/dvb/frontend.h>
#include "tvhead.h"
#include "dvb_support.h"
#include "strtab.h"
#include "dvb.h"
/*
* DVB String conversion according to EN 300 468, Annex A
@ -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;
}

View file

@ -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 */

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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 ?: "<no current event>");
remain = e->e_start + e->e_duration - time(NULL);
remain /= 60;
switch(e->e_source) {
case EVENT_SRC_XMLTV:
txt = "xmltv";
break;
case EVENT_SRC_DVB:
txt = "dvb";
break;
default:
txt = "???";
break;
}
cprintf(c, "\tNow: %-40s %2d:%02d [%s] tag: %d\n",
tmp, remain / 60, remain % 60, txt, e->e_tag);
free(tmp);
}
epg_unlock();
LIST_FOREACH(t, &ch->ch_transports, tht_channel_link) {
cprintf(c, "\t%-47s", t->tht_name);
switch(t->tht_status) {
case TRANSPORT_IDLE:
cprintf(c, "idle\n");
break;
case TRANSPORT_RUNNING:
v = avgstat_read_and_expire(&t->tht_rate, dispatch_clock);
cprintf(c, "running (%d kb/s)\n",
v * 8 / 1000 / 10);
break;
default:
continue;
}
v = avgstat_read(&t->tht_cc_errors, 60, dispatch_clock);
if(v)
cprintf(c, "\t\t%d error%s last minute %f / second\n",
v, v == 1 ? "" : "s", v / 60.);
v = avgstat_read_and_expire(&t->tht_cc_errors, dispatch_clock);
if(v)
cprintf(c, "\t\t%d error%s last hour %f / second\n",
v, v == 1 ? "" : "s", v / 3600.);
LIST_FOREACH(s, &t->tht_subscriptions, ths_transport_link) {
cprintf(c, "\t\t%s @ prio %d, since %s",
s->ths_title, s->ths_weight, ctime(&s->ths_start));
if(s->ths_total_err) {
cprintf(c,"\t\t\t%d error%s seen\n",
s->ths_total_err, s->ths_total_err == 1 ? "" : "s");
}
}
}
cprintf(c, "\n");
}
return 0;
}
if(!strcasecmp(subcmd, "dvbadapters")) {
LIST_FOREACH(tda, &dvb_adapters_running, tda_link) {
cprintf(c, "%20s: ", tda->tda_rootpath);
tdmi = tda->tda_mux_current;
if(tdmi == NULL) {
cprintf(c, "inactive\n");
continue;
}
cprintf(c, "Tuned to \"%s\"\n", tdmi->tdmi_shortname);
cprintf(c, "\t\t ");
print_tdmi(c, tda->tda_mux_current);
cprintf(c, "\n");
}
return 0;
}
if(!strcasecmp(subcmd, "storage")) {
cprintf(c, "In-memory storage %lld / %lld\n",
store_mem_size, store_mem_size_max);
cprintf(c, " On-disk storage %lld / %lld\n",
store_disk_size, store_disk_size_max);
cprintf(c, " %d packets in memory\n",
store_packets);
return 0;
}
return 1;
}
/*
*
*/
@ -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 },

2
http.c
View file

@ -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:

View file

@ -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)

33
main.c
View file

@ -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 "<unknown>";
}
}
FILE *
settings_open_for_write(const char *name)
{

107
psi.c
View file

@ -23,6 +23,7 @@
#include <string.h>
#include <libavutil/common.h>
#include <libavutil/avstring.h>
#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);
}
}

3
psi.h
View file

@ -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_ */

View file

@ -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);
}

View file

@ -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 */

View file

@ -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));

View file

@ -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 */

10
v4l.c
View file

@ -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;