/* * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tvhead.h" #include "dispatch.h" #include "dvb.h" #include "channels.h" #include "transports.h" #include "subscriptions.h" #include "teletext.h" #include "epg.h" #include "psi.h" #include "dvb_support.h" #include "dvb_dvr.h" #include "dvb_muxconfig.h" struct th_dvb_mux_list dvb_muxes; struct th_dvb_adapter_list dvb_adapters_probing; struct th_dvb_adapter_list dvb_adapters_running; static void dvb_start_initial_scan(th_dvb_mux_instance_t *tdmi); static void tdmi_activate(th_dvb_mux_instance_t *tdmi); static void dvb_mux_scanner(void *aux, int64_t now); static void dvb_fec_monitor(void *aux, int64_t now); static void dvb_add_adapter(const char *path) { char fname[256]; int fe; th_dvb_adapter_t *tda; snprintf(fname, sizeof(fname), "%s/frontend0", path); fe = open(fname, O_RDWR | O_NONBLOCK); if(fe == -1) { if(errno != ENOENT) syslog(LOG_ALERT, "Unable to open %s -- %s\n", fname, strerror(errno)); return; } tda = calloc(1, sizeof(th_dvb_adapter_t)); tda->tda_path = 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)) { syslog(LOG_ALERT, "%s: Unable to query adapter\n", fname); close(fe); free(tda); return; } if(dvb_dvr_init(tda) < 0) { close(fe); free(tda); return; } pthread_mutex_init(&tda->tda_lock, NULL); pthread_cond_init(&tda->tda_cond, NULL); TAILQ_INIT(&tda->tda_fe_cmd_queue); LIST_INSERT_HEAD(&dvb_adapters_probing, tda, tda_link); startupcounter++; tda->tda_info = strdup(tda->tda_fe_info->name); syslog(LOG_INFO, "Adding adapter %s (%s)", path, tda->tda_fe_info->name); dtimer_arm(&tda->tda_fec_monitor_timer, dvb_fec_monitor, tda, 1); dvb_fe_start(tda); } void dvb_init(void) { th_dvb_adapter_t *tda; th_dvb_mux_instance_t *tdmi; char path[200]; int i; for(i = 0; i < 32; i++) { snprintf(path, sizeof(path), "/dev/dvb/adapter%d", i); dvb_add_adapter(path); } dvb_mux_setup(); LIST_FOREACH(tda, &dvb_adapters_probing, tda_link) { tdmi = LIST_FIRST(&tda->tda_muxes_configured); if(tdmi == NULL) { syslog(LOG_WARNING, "No muxes configured on \"%s\" DVB adapter unused", tda->tda_path); startupcounter--; } else { dvb_start_initial_scan(tdmi); } } } /** * Based on the gived transport id and service id, try to locate the transport */ th_transport_t * dvb_find_transport(th_dvb_mux_instance_t *tdmi, uint16_t tid, uint16_t sid, int pmt_pid) { th_transport_t *t; th_dvb_mux_t *tdm = tdmi->tdmi_mux; char tmp[100]; LIST_FOREACH(t, &all_transports, tht_global_link) { if(t->tht_dvb_transport_id == tid && t->tht_dvb_service_id == sid) return t; } if(pmt_pid == 0) return NULL; t = calloc(1, sizeof(th_transport_t)); transport_monitor_init(t); t->tht_dvb_transport_id = tid; t->tht_dvb_service_id = sid; t->tht_type = TRANSPORT_DVB; t->tht_start_feed = dvb_start_feed; t->tht_stop_feed = dvb_stop_feed; t->tht_dvb_mux = tdm; snprintf(tmp, sizeof(tmp), "%s:%04x:%04x", tdm->tdm_name, tid, sid); free((void *)t->tht_uniquename); t->tht_uniquename = strdup(tmp); t->tht_name = strdup(tdm->tdm_title); LIST_INSERT_HEAD(&all_transports, t, tht_global_link); dvb_table_add_transport(tdmi, t, pmt_pid); return t; } /** * */ static void tdmi_activate(th_dvb_mux_instance_t *tdmi) { th_dvb_adapter_t *tda = tdmi->tdmi_adapter; dtimer_disarm(&tdmi->tdmi_initial_scan_timer); tdmi->tdmi_state = TDMI_IDLE; LIST_REMOVE(tdmi, tdmi_adapter_link); LIST_INSERT_HEAD(&tda->tda_muxes_active, tdmi, tdmi_adapter_link); /* tune to next configured (but not yet active) mux */ tdmi = LIST_FIRST(&tda->tda_muxes_configured); if(tdmi == NULL) { startupcounter--; syslog(LOG_INFO, "\"%s\" Initial scan completed, adapter available", tda->tda_path); /* no more muxes to probe, link adapter to the world */ LIST_REMOVE(tda, tda_link); LIST_INSERT_HEAD(&dvb_adapters_running, tda, tda_link); dtimer_arm(&tda->tda_mux_scanner_timer, dvb_mux_scanner, tda, 10); return; } dvb_start_initial_scan(tdmi); } /** * */ static void tdmi_initial_scan_timeout(void *aux, int64_t now) { th_dvb_mux_instance_t *tdmi = aux; th_dvb_adapter_t *tda = tdmi->tdmi_adapter; const char *err; #if 0 th_dvb_table_t *tdt; LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link) { printf("%s: %d\n", tdt->tdt_name, tdt->tdt_count); } #endif dtimer_disarm(&tdmi->tdmi_initial_scan_timer); if(tdmi->tdmi_status != NULL) err = tdmi->tdmi_status; else err = "Missing PSI tables, scan will continue"; syslog(LOG_DEBUG, "\"%s\" mux \"%s\" Initial scan timed out -- %s", tda->tda_path, tdmi->tdmi_mux->tdm_name, err); tdmi_activate(tdmi); } /** * */ void tdmi_check_scan_status(th_dvb_mux_instance_t *tdmi) { th_dvb_table_t *tdt; th_dvb_adapter_t *tda = tdmi->tdmi_adapter; if(tdmi->tdmi_state >= TDMI_IDLE) return; LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link) if(tdt->tdt_count == 0) return; /* All tables seen at least once */ syslog(LOG_DEBUG, "\"%s\" on \"%s\" Initial scan completed", tda->tda_path, tdmi->tdmi_mux->tdm_name); tdmi_activate(tdmi); } /** * */ static void dvb_start_initial_scan(th_dvb_mux_instance_t *tdmi) { dvb_tune_tdmi(tdmi, 1, TDMI_INITIAL_SCAN); dtimer_arm(&tdmi->tdmi_initial_scan_timer, tdmi_initial_scan_timeout, tdmi, 5); } /** * */ static int mux_sort_quality(th_dvb_mux_instance_t *a, th_dvb_mux_instance_t *b) { return a->tdmi_fec_err_per_sec - b->tdmi_fec_err_per_sec; } /** * */ static void dvb_fec_monitor(void *aux, int64_t now) { th_dvb_adapter_t *tda = aux; th_dvb_mux_instance_t *tdmi; th_dvb_mux_t *tdm; dtimer_arm(&tda->tda_fec_monitor_timer, dvb_fec_monitor, tda, 1); tdmi = tda->tda_mux_current; if(tdmi != NULL && tdmi->tdmi_status == NULL) { if(tdmi->tdmi_fec_err_per_sec > DVB_FEC_ERROR_LIMIT) { if(LIST_FIRST(&tda->tda_transports) != NULL) { syslog(LOG_ERR, "%s: on %s: Too many FEC errors (%d / s), " "flushing subscribers\n", tdmi->tdmi_mux->tdm_name, tda->tda_path, tdmi->tdmi_fec_err_per_sec); dvb_adapter_clean(tdmi->tdmi_adapter); } } tdm = tdmi->tdmi_mux; LIST_REMOVE(tdmi, tdmi_mux_link); LIST_INSERT_SORTED(&tdm->tdm_instances, tdmi, tdmi_mux_link, mux_sort_quality); } } /** * If nobody is subscribing, cycle thru all muxes to get some stats * and EIT updates */ static void dvb_mux_scanner(void *aux, int64_t now) { th_dvb_adapter_t *tda = aux; th_dvb_mux_instance_t *tdmi; dtimer_arm(&tda->tda_mux_scanner_timer, dvb_mux_scanner, tda, 10); if(transport_compute_weight(&tda->tda_transports) > 0) return; /* someone is here */ tdmi = tda->tda_mux_current; tdmi = tdmi != NULL ? LIST_NEXT(tdmi, tdmi_adapter_link) : NULL; tdmi = tdmi != NULL ? tdmi : LIST_FIRST(&tda->tda_muxes_active); if(tdmi == NULL) return; /* no instances */ dvb_tune_tdmi(tdmi, 0, TDMI_IDLESCAN); }