tvheadend/src/dvb/dvb_adapter.c
2009-08-15 21:45:34 +00:00

678 lines
16 KiB
C

/*
* TV Input - Linux DVB interface
* Copyright (C) 2007 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 <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include "settings.h"
#include "tvhead.h"
#include "transports.h"
#include "dvb.h"
#include "dvb_support.h"
#include "tsdemux.h"
#include "notify.h"
struct th_dvb_adapter_queue dvb_adapters;
struct th_dvb_mux_instance_tree dvb_muxes;
static void *dvb_adapter_input_dvr(void *aux);
/**
*
*/
static th_dvb_adapter_t *
tda_alloc(void)
{
th_dvb_adapter_t *tda = calloc(1, sizeof(th_dvb_adapter_t));
pthread_mutex_init(&tda->tda_delivery_mutex, NULL);
TAILQ_INIT(&tda->tda_scan_queues[0]);
TAILQ_INIT(&tda->tda_scan_queues[1]);
TAILQ_INIT(&tda->tda_initial_scan_queue);
TAILQ_INIT(&tda->tda_satconfs);
return tda;
}
/**
* Save config for the given adapter
*/
static void
tda_save(th_dvb_adapter_t *tda)
{
htsmsg_t *m = htsmsg_create_map();
lock_assert(&global_lock);
htsmsg_add_str(m, "type", dvb_adaptertype_to_str(tda->tda_type));
htsmsg_add_str(m, "displayname", tda->tda_displayname);
htsmsg_add_u32(m, "autodiscovery", tda->tda_autodiscovery);
htsmsg_add_u32(m, "idlescan", tda->tda_idlescan);
htsmsg_add_u32(m, "logging", tda->tda_logging);
hts_settings_save(m, "dvbadapters/%s", tda->tda_identifier);
htsmsg_destroy(m);
}
/**
*
*/
void
dvb_adapter_set_displayname(th_dvb_adapter_t *tda, const char *s)
{
lock_assert(&global_lock);
if(!strcmp(s, tda->tda_displayname))
return;
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" renamed to \"%s\"",
tda->tda_displayname, s);
free(tda->tda_displayname);
tda->tda_displayname = strdup(s);
tda_save(tda);
dvb_adapter_notify(tda);
}
/**
*
*/
void
dvb_adapter_set_auto_discovery(th_dvb_adapter_t *tda, int on)
{
if(tda->tda_autodiscovery == on)
return;
lock_assert(&global_lock);
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" mux autodiscovery set to: %s",
tda->tda_displayname, on ? "On" : "Off");
tda->tda_autodiscovery = on;
tda_save(tda);
}
/**
*
*/
void
dvb_adapter_set_idlescan(th_dvb_adapter_t *tda, int on)
{
if(tda->tda_idlescan == on)
return;
lock_assert(&global_lock);
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" idle mux scanning set to: %s",
tda->tda_displayname, on ? "On" : "Off");
tda->tda_idlescan = on;
tda_save(tda);
}
/**
*
*/
void
dvb_adapter_set_logging(th_dvb_adapter_t *tda, int on)
{
if(tda->tda_logging == on)
return;
lock_assert(&global_lock);
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" detailed logging set to: %s",
tda->tda_displayname, on ? "On" : "Off");
tda->tda_logging = on;
tda_save(tda);
}
/**
*
*/
static void
tda_add(const char *path)
{
char fname[256];
int fe, i, r;
th_dvb_adapter_t *tda;
char buf[400];
pthread_t ptid;
snprintf(fname, sizeof(fname), "%s/frontend0", path);
fe = open(fname, O_RDWR | O_NONBLOCK);
if(fe == -1) {
if(errno != ENOENT)
tvhlog(LOG_ALERT, "dvb",
"Unable to open %s -- %s", fname, strerror(errno));
return;
}
tda = tda_alloc();
tda->tda_rootpath = strdup(path);
tda->tda_demux_path = malloc(256);
snprintf(tda->tda_demux_path, 256, "%s/demux0", path);
tda->tda_dvr_path = malloc(256);
snprintf(tda->tda_dvr_path, 256, "%s/dvr0", path);
tda->tda_fe_fd = fe;
tda->tda_fe_info = malloc(sizeof(struct dvb_frontend_info));
if(ioctl(tda->tda_fe_fd, FE_GET_INFO, tda->tda_fe_info)) {
tvhlog(LOG_ALERT, "dvb", "%s: Unable to query adapter", fname);
close(fe);
free(tda);
return;
}
tda->tda_type = tda->tda_fe_info->type;
snprintf(buf, sizeof(buf), "%s_%s", tda->tda_rootpath,
tda->tda_fe_info->name);
r = strlen(buf);
for(i = 0; i < r; i++)
if(!isalnum((int)buf[i]))
buf[i] = '_';
tda->tda_identifier = strdup(buf);
tda->tda_autodiscovery = tda->tda_type != FE_QPSK;
tda->tda_idlescan = 1;
tda->tda_sat = tda->tda_type == FE_QPSK;
/* Come up with an initial displayname, user can change it and it will
be overridden by any stored settings later on */
tda->tda_displayname = strdup(tda->tda_fe_info->name);
tvhlog(LOG_INFO, "dvb",
"Found adapter %s (%s)", path, tda->tda_fe_info->name);
TAILQ_INSERT_TAIL(&dvb_adapters, tda, tda_global_link);
pthread_create(&ptid, NULL, dvb_adapter_input_dvr, tda);
dvb_table_init(tda);
if(tda->tda_sat)
dvb_satconf_init(tda);
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 1);
}
/**
*
*/
void
dvb_adapter_init(void)
{
char path[200];
htsmsg_t *l, *c;
htsmsg_field_t *f;
const char *name, *s;
int i, type;
th_dvb_adapter_t *tda;
TAILQ_INIT(&dvb_adapters);
for(i = 0; i < 32; i++) {
snprintf(path, sizeof(path), "/dev/dvb/adapter%d", i);
tda_add(path);
}
l = hts_settings_load("dvbadapters");
if(l != NULL) {
HTSMSG_FOREACH(f, l) {
if((c = htsmsg_get_map_by_field(f)) == NULL)
continue;
name = htsmsg_get_str(c, "displayname");
if((s = htsmsg_get_str(c, "type")) == NULL ||
(type = dvb_str_to_adaptertype(s)) < 0)
continue;
if((tda = dvb_adapter_find_by_identifier(f->hmf_name)) == NULL) {
/* Not discovered by hardware, create it */
tda = tda_alloc();
tda->tda_identifier = strdup(f->hmf_name);
tda->tda_type = type;
TAILQ_INSERT_TAIL(&dvb_adapters, tda, tda_global_link);
} else {
if(type != tda->tda_type)
continue; /* Something is wrong, ignore */
}
free(tda->tda_displayname);
tda->tda_displayname = strdup(name);
htsmsg_get_u32(c, "autodiscovery", &tda->tda_autodiscovery);
htsmsg_get_u32(c, "idlescan", &tda->tda_idlescan);
htsmsg_get_u32(c, "logging", &tda->tda_logging);
}
htsmsg_destroy(l);
}
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
dvb_mux_load(tda);
}
/**
* If nobody is subscribing, cycle thru all muxes to get some stats
* and EIT updates
*/
void
dvb_adapter_mux_scanner(void *aux)
{
th_dvb_adapter_t *tda = aux;
th_dvb_mux_instance_t *tdmi;
int i;
if(tda->tda_rootpath == NULL)
return; // No hardware
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 20);
if(LIST_FIRST(&tda->tda_muxes) == NULL)
return; // No muxes configured
if(transport_compute_weight(&tda->tda_transports) > 0)
return; /* someone is here */
/* Check if we have muxes pending for quickscan, if so, choose them */
if((tdmi = TAILQ_FIRST(&tda->tda_initial_scan_queue)) != NULL) {
dvb_fe_tune(tdmi, "Initial autoscan");
return;
}
if(!tda->tda_idlescan && TAILQ_FIRST(&tda->tda_scan_queues[0]) == NULL) {
/* Idlescan is disabled and no muxes are bad.
If the currently tuned mux is ok, we can stick to it */
tdmi = tda->tda_mux_current;
if(tdmi != NULL && tdmi->tdmi_quality > 90)
return;
}
/* Alternate between the other two (bad and OK) */
for(i = 0; i < 2; i++) {
tda->tda_scan_selector = !tda->tda_scan_selector;
tdmi = TAILQ_FIRST(&tda->tda_scan_queues[tda->tda_scan_selector]);
if(tdmi != NULL) {
dvb_fe_tune(tdmi, "Autoscan");
return;
}
}
}
/**
*
*/
void
dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src)
{
th_dvb_mux_instance_t *tdmi_src, *tdmi_dst;
th_transport_t *t_src, *t_dst;
th_stream_t *st_src, *st_dst;
lock_assert(&global_lock);
while((tdmi_dst = LIST_FIRST(&dst->tda_muxes)) != NULL)
dvb_mux_destroy(tdmi_dst);
LIST_FOREACH(tdmi_src, &src->tda_muxes, tdmi_adapter_link) {
tdmi_dst = dvb_mux_create(dst,
&tdmi_src->tdmi_fe_params,
tdmi_src->tdmi_polarisation,
tdmi_src->tdmi_satconf,
tdmi_src->tdmi_transport_stream_id,
tdmi_src->tdmi_network,
"copy operation", tdmi_src->tdmi_enabled,
NULL);
assert(tdmi_dst != NULL);
LIST_FOREACH(t_src, &tdmi_src->tdmi_transports, tht_group_link) {
t_dst = dvb_transport_find(tdmi_dst,
t_src->tht_dvb_service_id,
t_src->tht_pmt_pid, NULL);
t_dst->tht_pcr_pid = t_src->tht_pcr_pid;
t_dst->tht_enabled = t_src->tht_enabled;
t_dst->tht_servicetype = t_src->tht_servicetype;
t_dst->tht_scrambled = t_src->tht_scrambled;
if(t_src->tht_provider != NULL)
t_dst->tht_provider = strdup(t_src->tht_provider);
if(t_src->tht_svcname != NULL)
t_dst->tht_svcname = strdup(t_src->tht_svcname);
if(t_src->tht_ch != NULL)
transport_map_channel(t_dst, t_src->tht_ch, 0);
pthread_mutex_lock(&t_src->tht_stream_mutex);
LIST_FOREACH(st_src, &t_src->tht_components, st_link) {
st_dst = transport_stream_create(t_dst,
st_src->st_pid,
st_src->st_type);
st_dst->st_tb = (AVRational){1, 90000};
memcpy(st_dst->st_lang, st_src->st_lang, 4);
st_dst->st_frame_duration = st_src->st_frame_duration;
st_dst->st_caid = st_src->st_caid;
}
pthread_mutex_unlock(&t_src->tht_stream_mutex);
t_dst->tht_config_save(t_dst); // Save config
}
dvb_mux_save(tdmi_dst);
}
tda_save(dst);
}
/**
*
*/
int
dvb_adapter_destroy(th_dvb_adapter_t *tda)
{
th_dvb_mux_instance_t *tdmi;
if(tda->tda_rootpath != NULL)
return -1;
lock_assert(&global_lock);
hts_settings_remove("dvbadapters/%s", tda->tda_identifier);
while((tdmi = LIST_FIRST(&tda->tda_muxes)) != NULL)
dvb_mux_destroy(tdmi);
TAILQ_REMOVE(&dvb_adapters, tda, tda_global_link);
free(tda->tda_identifier);
free(tda->tda_displayname);
free(tda);
return 0;
}
/**
*
*/
void
dvb_adapter_clean(th_dvb_adapter_t *tda)
{
th_transport_t *t;
lock_assert(&global_lock);
while((t = LIST_FIRST(&tda->tda_transports)) != NULL)
transport_remove_subscriber(t, NULL); /* Flushes all subscribers */
}
/**
*
*/
static void *
dvb_adapter_input_dvr(void *aux)
{
th_dvb_adapter_t *tda = aux;
int fd, i, r;
uint8_t tsb[188 * 10];
th_transport_t *t;
fd = open(tda->tda_dvr_path, O_RDONLY);
if(fd == -1) {
tvhlog(LOG_ALERT, "dvb", "%s: unable to open dvr", tda->tda_dvr_path);
return NULL;
}
while(1) {
r = read(fd, tsb, sizeof(tsb));
pthread_mutex_lock(&tda->tda_delivery_mutex);
for(i = 0; i < r; i += 188)
LIST_FOREACH(t, &tda->tda_transports, tht_active_link)
if(t->tht_dvb_mux_instance == tda->tda_mux_current)
ts_recv_packet1(t, tsb + i);
pthread_mutex_unlock(&tda->tda_delivery_mutex);
}
}
static struct strtab deliverysystemtab[] = {
{"DVB-S", FE_QPSK},
{"DVB-T", FE_OFDM},
{"DVB-C", FE_QAM},
};
/**
*
*/
htsmsg_t *
dvb_adapter_build_msg(th_dvb_adapter_t *tda)
{
char buf[100];
htsmsg_t *m = htsmsg_create_map();
th_dvb_mux_instance_t *tdmi;
th_transport_t *t;
int nummux = 0;
int numsvc = 0;
int fdiv;
htsmsg_add_str(m, "identifier", tda->tda_identifier);
htsmsg_add_str(m, "name", tda->tda_displayname);
// XXX: bad bad bad slow slow slow
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
nummux++;
LIST_FOREACH(t, &tdmi->tdmi_transports, tht_group_link) {
numsvc++;
}
}
htsmsg_add_u32(m, "services", numsvc);
htsmsg_add_u32(m, "muxes", nummux);
htsmsg_add_u32(m, "initialMuxes", tda->tda_initial_num_mux);
if(tda->tda_mux_current != NULL) {
dvb_mux_nicename(buf, sizeof(buf), tda->tda_mux_current);
htsmsg_add_str(m, "currentMux", buf);
}
if(tda->tda_rootpath == NULL)
return m;
htsmsg_add_str(m, "path", tda->tda_rootpath);
htsmsg_add_str(m, "devicename", tda->tda_fe_info->name);
htsmsg_add_str(m, "deliverySystem",
val2str(tda->tda_type, deliverysystemtab) ?: "");
htsmsg_add_u32(m, "satConf", tda->tda_sat);
fdiv = tda->tda_type == FE_QPSK ? 1 : 1000;
htsmsg_add_u32(m, "freqMin", tda->tda_fe_info->frequency_min / fdiv);
htsmsg_add_u32(m, "freqMax", tda->tda_fe_info->frequency_max / fdiv);
htsmsg_add_u32(m, "freqStep", tda->tda_fe_info->frequency_stepsize / fdiv);
htsmsg_add_u32(m, "symrateMin", tda->tda_fe_info->symbol_rate_min);
htsmsg_add_u32(m, "symrateMax", tda->tda_fe_info->symbol_rate_max);
return m;
}
/**
*
*/
void
dvb_adapter_notify(th_dvb_adapter_t *tda)
{
notify_by_msg("dvbAdapter", dvb_adapter_build_msg(tda));
}
/**
*
*/
static void
fe_opts_add(htsmsg_t *a, const char *title, int value)
{
htsmsg_t *v = htsmsg_create_map();
htsmsg_add_str(v, "title", title);
htsmsg_add_u32(v, "id", value);
htsmsg_add_msg(a, NULL, v);
}
/**
*
*/
htsmsg_t *
dvb_fe_opts(th_dvb_adapter_t *tda, const char *which)
{
htsmsg_t *a = htsmsg_create_list();
fe_caps_t c = tda->tda_fe_info->caps;
if(!strcmp(which, "constellations")) {
if(c & FE_CAN_QAM_AUTO) fe_opts_add(a, "Auto", QAM_AUTO);
if(c & FE_CAN_QPSK) fe_opts_add(a, "QPSK", QPSK);
if(c & FE_CAN_QAM_16) fe_opts_add(a, "QAM-16", QAM_16);
if(c & FE_CAN_QAM_32) fe_opts_add(a, "QAM-32", QAM_32);
if(c & FE_CAN_QAM_64) fe_opts_add(a, "QAM-64", QAM_64);
if(c & FE_CAN_QAM_128) fe_opts_add(a, "QAM-128", QAM_128);
if(c & FE_CAN_QAM_256) fe_opts_add(a, "QAM-256", QAM_256);
return a;
}
if(!strcmp(which, "transmissionmodes")) {
if(c & FE_CAN_TRANSMISSION_MODE_AUTO)
fe_opts_add(a, "Auto", TRANSMISSION_MODE_AUTO);
fe_opts_add(a, "2k", TRANSMISSION_MODE_2K);
fe_opts_add(a, "8k", TRANSMISSION_MODE_8K);
return a;
}
if(!strcmp(which, "bandwidths")) {
if(c & FE_CAN_BANDWIDTH_AUTO)
fe_opts_add(a, "Auto", BANDWIDTH_AUTO);
fe_opts_add(a, "8 MHz", BANDWIDTH_8_MHZ);
fe_opts_add(a, "7 MHz", BANDWIDTH_7_MHZ);
fe_opts_add(a, "6 MHz", BANDWIDTH_6_MHZ);
return a;
}
if(!strcmp(which, "guardintervals")) {
if(c & FE_CAN_GUARD_INTERVAL_AUTO)
fe_opts_add(a, "Auto", GUARD_INTERVAL_AUTO);
fe_opts_add(a, "1/32", GUARD_INTERVAL_1_32);
fe_opts_add(a, "1/16", GUARD_INTERVAL_1_16);
fe_opts_add(a, "1/8", GUARD_INTERVAL_1_8);
fe_opts_add(a, "1/4", GUARD_INTERVAL_1_4);
return a;
}
if(!strcmp(which, "hierarchies")) {
if(c & FE_CAN_HIERARCHY_AUTO)
fe_opts_add(a, "Auto", HIERARCHY_AUTO);
fe_opts_add(a, "None", HIERARCHY_NONE);
fe_opts_add(a, "1", HIERARCHY_1);
fe_opts_add(a, "2", HIERARCHY_2);
fe_opts_add(a, "4", HIERARCHY_4);
return a;
}
if(!strcmp(which, "fec")) {
if(c & FE_CAN_FEC_AUTO)
fe_opts_add(a, "Auto", FEC_AUTO);
fe_opts_add(a, "None", FEC_NONE);
fe_opts_add(a, "1/2", FEC_1_2);
fe_opts_add(a, "2/3", FEC_2_3);
fe_opts_add(a, "3/4", FEC_3_4);
fe_opts_add(a, "4/5", FEC_4_5);
fe_opts_add(a, "5/6", FEC_5_6);
fe_opts_add(a, "6/7", FEC_6_7);
fe_opts_add(a, "7/8", FEC_7_8);
fe_opts_add(a, "8/9", FEC_8_9);
return a;
}
if(!strcmp(which, "polarisations")) {
fe_opts_add(a, "Horizontal", POLARISATION_HORIZONTAL);
fe_opts_add(a, "Vertical", POLARISATION_VERTICAL);
return a;
}
htsmsg_destroy(a);
return NULL;
}