/* * Tvheadend - Linux DVB frontend * * Copyright (C) 2013 Adam Sutton * * 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 "tvheadend.h" #include "linuxdvb_private.h" #include #include #include #include #include #include #include #include static int linuxdvb_frontend_tune ( linuxdvb_frontend_t *lfe, linuxdvb_mux_t *lm ); static void linuxdvb_frontend_monitor ( void *aux ); static void * linuxdvb_frontend_input_thread ( void *aux ); /* ************************************************************************** * Class definition * *************************************************************************/ extern const idclass_t linuxdvb_hardware_class; static const char * linuxdvb_frontend_class_get_title ( idnode_t *in ) { linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)in; if (lfe->lh_displayname) return lfe->lh_displayname; if (lfe->lfe_fe_path) return lfe->lfe_fe_path; return "unknown"; } static void linuxdvb_frontend_class_save ( idnode_t *in ) { linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)in; linuxdvb_device_save((linuxdvb_device_t*)lfe->lh_parent->lh_parent); } const idclass_t linuxdvb_frontend_class = { .ic_super = &linuxdvb_hardware_class, .ic_class = "linuxdvb_frontend", .ic_caption = "Linux DVB Frontend", .ic_get_title = linuxdvb_frontend_class_get_title, .ic_save = linuxdvb_frontend_class_save, .ic_properties = (const property_t[]) { { PROPDEF2("fe_path", "Frontend Path", PT_STR, linuxdvb_frontend_t, lfe_fe_path, 1) }, { PROPDEF2("dvr_path", "Input Path", PT_STR, linuxdvb_frontend_t, lfe_dvr_path, 1) }, { PROPDEF2("dmx_path", "Demux Path", PT_STR, linuxdvb_frontend_t, lfe_dmx_path, 1) }, { PROPDEF2("number", "FE Number", PT_INT, linuxdvb_frontend_t, lfe_number, 1) }, { PROPDEF1("fullmux", "Full Mux Mode", PT_BOOL, linuxdvb_frontend_t, lfe_fullmux) }, {} } }; const idclass_t linuxdvb_frontend_dvbt_class = { .ic_super = &linuxdvb_frontend_class, .ic_class = "linuxdvb_frontend_dvbt", .ic_caption = "Linux DVB-T Frontend", .ic_properties = (const property_t[]){ {} } }; const idclass_t linuxdvb_frontend_dvbs_class = { .ic_super = &linuxdvb_frontend_class, .ic_class = "linuxdvb_frontend_dvbs", .ic_caption = "Linux DVB-S Frontend", .ic_properties = (const property_t[]){ {} } }; const idclass_t linuxdvb_frontend_dvbc_class = { .ic_super = &linuxdvb_frontend_class, .ic_class = "linuxdvb_frontend_dvbc", .ic_caption = "Linux DVB-C Frontend", .ic_properties = (const property_t[]){ {} } }; const idclass_t linuxdvb_frontend_atsc_class = { .ic_super = &linuxdvb_frontend_class, .ic_class = "linuxdvb_frontend_atsc", .ic_caption = "Linux ATSC Frontend", .ic_properties = (const property_t[]){ {} } }; /* ************************************************************************** * Class methods * *************************************************************************/ static int linuxdvb_frontend_is_enabled ( mpegts_input_t *mi ) { linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; if (lfe->lfe_fe_path == NULL) return 0; if (!lfe->mi_enabled) return 0; if (access(lfe->lfe_fe_path, R_OK | W_OK)) return 0; return 1; } static void linuxdvb_frontend_display_name ( mpegts_input_t* mi, char *buf, size_t len ) { strncpy(buf, linuxdvb_frontend_class_get_title(&mi->mi_id), len); } #if 0 static int linuxdvb_frontend_is_free ( mpegts_input_t *mi ) { #if 0 linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; linuxdvb_adapter_t *la = lfe->lfe_adapter; return linuxdvb_adapter_is_free(la); #endif return 0; } static int linuxdvb_frontend_current_weight ( mpegts_input_t *mi ) { #if 0 linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; linuxdvb_adapter_t *la = lfe->lfe_adapter; return linuxdvb_adapter_current_weight(la); #endif return 0; } #endif static void linuxdvb_frontend_stop_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi ) { char buf1[256], buf2[256]; linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; mi->mi_display_name(mi, buf1, sizeof(buf1)); mmi->mmi_mux->mm_display_name(mmi->mmi_mux, buf2, sizeof(buf2)); tvhdebug("linuxdvb", "%s - stopping %s", buf1, buf2); /* Stop thread */ if (lfe->lfe_dvr_pipe.wr > 0) { tvh_write(lfe->lfe_dvr_pipe.wr, "", 1); tvhtrace("linuxdvb", "%s - waiting for dvr thread", buf1); pthread_join(lfe->lfe_dvr_thread, NULL); tvh_pipe_close(&lfe->lfe_dvr_pipe); tvhdebug("linuxdvb", "%s - stopped dvr thread", buf1); } /* Not locked */ lfe->lfe_locked = 0; } static int linuxdvb_frontend_start_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi ) { int r; char buf1[256], buf2[256]; linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; mpegts_mux_instance_t *cur = LIST_FIRST(&mi->mi_mux_active); mi->mi_display_name(mi, buf1, sizeof(buf1)); mmi->mmi_mux->mm_display_name(mmi->mmi_mux, buf2, sizeof(buf2)); tvhdebug("linuxdvb", "%s - starting %s", buf1, buf2); // Not sure if this is right place? /* Currently active */ if (cur != NULL) { /* Already tuned */ if (mmi == cur) return 0; /* Stop current */ cur->mmi_mux->mm_stop(cur->mmi_mux); } assert(LIST_FIRST(&mi->mi_mux_active) == NULL); /* Open FE */ if (lfe->lfe_fe_fd <= 0) { tvhtrace("linuxdvb", "%s - opening FE %s", buf1, lfe->lfe_fe_path); lfe->lfe_fe_fd = tvh_open(lfe->lfe_fe_path, O_RDWR | O_NONBLOCK, 0); if (lfe->lfe_fe_fd <= 0) { return SM_CODE_TUNING_FAILED; } } /* Tune */ tvhtrace("linuxdvb", "%s - tuning", buf1); r = linuxdvb_frontend_tune(lfe, (linuxdvb_mux_t*)mmi->mmi_mux); /* Failed */ if (r != 0) { tvherror("linuxdvb", "%s - failed to tune [e=%s]", buf1, strerror(errno)); if (errno == EINVAL) mmi->mmi_tune_failed = 1; return SM_CODE_TUNING_FAILED; } /* Start monitor */ time(&lfe->lfe_monitor); lfe->lfe_monitor += 4; gtimer_arm_ms(&lfe->lfe_monitor_timer, linuxdvb_frontend_monitor, lfe, 50); return r; } static int linuxdvb_frontend_open_pid ( linuxdvb_frontend_t *lfe, int pid, const char *name ) { char buf[256]; struct dmx_pes_filter_params dmx_param; int fd = tvh_open(lfe->lfe_dmx_path, O_RDWR, 0); if (!name) { lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf)); name = buf; } if(fd == -1) { tvherror("linuxdvb", "%s - failed to open dmx for pid %d [e=%s]", name, pid, strerror(errno)); return -1; } memset(&dmx_param, 0, sizeof(dmx_param)); dmx_param.pid = pid; dmx_param.input = DMX_IN_FRONTEND; dmx_param.output = DMX_OUT_TS_TAP; dmx_param.pes_type = DMX_PES_OTHER; dmx_param.flags = DMX_IMMEDIATE_START; if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) { tvherror("linuxdvb", "%s - failed to config dmx for pid %d [e=%s]", name, pid, strerror(errno)); close(fd); return -1; } return fd; } static void linuxdvb_frontend_open_service ( mpegts_input_t *mi, mpegts_service_t *s ) { char buf[256]; elementary_stream_t *st; linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; /* Ignore in full rx mode OR if not yet locked */ if (lfe->lfe_fullmux || !lfe->lfe_locked) return; mi->mi_display_name(mi, buf, sizeof(buf)); /* Install PES filters */ TAILQ_FOREACH(st, &s->s_components, es_link) { if(st->es_pid >= 0x2000) continue; if(st->es_demuxer_fd != -1) continue; st->es_cc_valid = 0; st->es_demuxer_fd = linuxdvb_frontend_open_pid((linuxdvb_frontend_t*)mi, st->es_pid, buf); } } static void linuxdvb_frontend_close_service ( mpegts_input_t *mi, mpegts_service_t *s ) { linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; /* Ignore in full rx mode OR if not yet locked */ if (lfe->lfe_fullmux || !lfe->lfe_locked) return; } /* ************************************************************************** * Data processing * *************************************************************************/ static void linuxdvb_frontend_default_tables ( linuxdvb_frontend_t *lfe, linuxdvb_mux_t *lm ) { mpegts_mux_t *mm = (mpegts_mux_t*)lfe; /* Common */ mpegts_table_add(mm, DVB_PAT_BASE, DVB_PAT_MASK, dvb_pat_callback, NULL, "pat", MT_QUICKREQ | MT_CRC, DVB_PAT_PID); #if 0 mpegts_table_add(mm, DVB_CAT_BASE, DVB_CAT_MASK, dvb_cat_callback, NULL, "cat", MT_CRC, DVB_CAT_PID); #endif /* ATSC */ if (lfe->lfe_info.type == FE_ATSC) { #if 0 int tableid; if (lc->lm_tuning.dmc_fe_params.u.vsb.modulation == VSB_8) tableid = ATSC_VCT_TERR; else tableid = ATSC_VCT_CAB; mpegts_table_add(mm, tableid, 0xff, atsc_vct_callback, NULL, "vct", MT_QUICKREQ | MT_CRC, ATSC_VCT_PID); #endif /* DVB */ } else { mpegts_table_add(mm, DVB_NIT_BASE, DVB_NIT_MASK, dvb_nit_callback, NULL, "nit", MT_QUICKREQ | MT_CRC, DVB_NIT_PID); mpegts_table_add(mm, DVB_SDT_BASE, DVB_SDT_MASK, dvb_sdt_callback, NULL, "sdt", MT_QUICKREQ | MT_CRC, DVB_SDT_PID); mpegts_table_add(mm, DVB_BAT_BASE, DVB_BAT_MASK, dvb_bat_callback, NULL, "bat", MT_CRC, DVB_BAT_PID); #if 0 mpegts_table_add(mm, DVB_TOT_BASE, DVB_TOT_MASK, dvb_tot_callback, NULL, "tot", MT_CRC, DVB_TOT_PID); #endif } } static void linuxdvb_frontend_open_services ( linuxdvb_frontend_t *lfe ) { service_t *s; LIST_FOREACH(s, &lfe->mi_transports, s_active_link) linuxdvb_frontend_open_service((mpegts_input_t*)lfe, (mpegts_service_t*)s); } static void linuxdvb_frontend_monitor_stats ( linuxdvb_frontend_t *lfe ) { } static void linuxdvb_frontend_monitor ( void *aux ) { linuxdvb_frontend_t *lfe = aux; mpegts_mux_instance_t *mmi = LIST_FIRST(&lfe->mi_mux_active); mpegts_mux_t *mm; fe_status_t fe_status; signal_state_t status; // TODO: check the frontend is accessible if (!mmi) return; mm = mmi->mmi_mux; /* Get current status */ if (!ioctl(lfe->lfe_fe_fd, FE_READ_STATUS, &fe_status)) status = SIGNAL_UNKNOWN; else if (fe_status & FE_HAS_LOCK) status = SIGNAL_GOOD; else if (fe_status & (FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_CARRIER)) status = SIGNAL_BAD; else if (fe_status & FE_HAS_SIGNAL) status = SIGNAL_FAINT; else status = SIGNAL_NONE; /* Set default period */ gtimer_arm(&lfe->lfe_monitor_timer, linuxdvb_frontend_monitor, lfe, 1); /* Waiting for lock */ if (lfe->lfe_locked) { /* Locked */ if (status == SIGNAL_GOOD) { lfe->lfe_locked = 1; /* Start input */ tvh_pipe(O_NONBLOCK, &lfe->lfe_dvr_pipe); pthread_create(&lfe->lfe_dvr_thread, NULL, linuxdvb_frontend_input_thread, lfe); /* Table handlers */ linuxdvb_frontend_default_tables(lfe, (linuxdvb_mux_t*)mm); /* Services */ linuxdvb_frontend_open_services(lfe); /* Re-arm (quick) */ } else { gtimer_arm_ms(&lfe->lfe_monitor_timer, linuxdvb_frontend_monitor, lfe, 50); /* Monitor 1 per sec */ if (dispatch_clock < lfe->lfe_monitor) return; lfe->lfe_monitor = dispatch_clock + 1; } } /* Monitor stats */ linuxdvb_frontend_monitor_stats(lfe); } static void * linuxdvb_frontend_input_thread ( void *aux ) { linuxdvb_frontend_t *lfe = aux; mpegts_mux_instance_t *mmi; int dmx = -1, dvr = -1; char buf[256]; uint8_t tsb[18800]; int pos = 0, nfds, efd; ssize_t c; struct epoll_event ev; struct dmx_pes_filter_params dmx_param; int fullmux; /* Get MMI */ pthread_mutex_lock(&global_lock); lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf)); mmi = LIST_FIRST(&lfe->mi_mux_active); fullmux = lfe->lfe_fullmux; pthread_mutex_unlock(&global_lock); if (mmi == NULL) return NULL; /* Open DMX */ if (fullmux) { dmx = tvh_open(lfe->lfe_dmx_path, O_RDWR, 0); if (dmx < 0) { tvherror("linuxdvb", "%s - failed to open %s", buf, lfe->lfe_dmx_path); return NULL; } memset(&dmx_param, 0, sizeof(dmx_param)); dmx_param.pid = 0x2000; dmx_param.input = DMX_IN_FRONTEND; dmx_param.output = DMX_OUT_TS_TAP; dmx_param.pes_type = DMX_PES_OTHER; dmx_param.flags = DMX_IMMEDIATE_START; if(ioctl(dmx, DMX_SET_PES_FILTER, &dmx_param) == -1) { tvherror("linuxdvb", "%s - open raw filter failed [e=%s]", buf, strerror(errno)); close(dmx); return NULL; } } /* Open DVR */ dvr = tvh_open(lfe->lfe_dvr_path, O_RDONLY | O_NONBLOCK, 0); if (dvr < 0) { close(dmx); tvherror("linuxdvb", "%s - failed to open %s", buf, lfe->lfe_dvr_path); return NULL; } /* Setup poll */ efd = epoll_create(2); memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.fd = dvr; epoll_ctl(efd, EPOLL_CTL_ADD, ev.data.fd, &ev); ev.events = EPOLLIN; ev.data.fd = lfe->lfe_dvr_pipe.rd; epoll_ctl(efd, EPOLL_CTL_ADD, ev.data.fd, &ev); /* Read */ while (1) { nfds = epoll_wait(efd, &ev, 1, 10); if (nfds < 1) continue; if (ev.data.fd != dvr) break; /* Read */ c = read(dvr, tsb+pos, sizeof(tsb)-pos); if (c < 0) { if ((errno == EAGAIN) || (errno == EINTR)) continue; if (errno == EOVERFLOW) { tvhlog(LOG_WARNING, "linuxdvb", "%s - read() EOVERFLOW", buf); continue; } tvhlog(LOG_ERR, "linuxdvb", "%s - read() error %d (%s)", buf, errno, strerror(errno)); break; } /* Process */ pos = mpegts_input_recv_packets((mpegts_input_t*)lfe, mmi, tsb, c, NULL, NULL, buf); } if (dmx != -1) close(dmx); close(dvr); return NULL; } /* ************************************************************************** * Tuning * *************************************************************************/ static int linuxdvb_frontend_tune ( linuxdvb_frontend_t *lfe, linuxdvb_mux_t *lm ) { int r; struct dvb_frontend_event ev; dvb_mux_conf_t *dmc = &lm->lm_tuning; struct dvb_frontend_parameters *p = &dmc->dmc_fe_params; /* S2 tuning */ #if DVB_API_VERSION >= 5 struct dtv_property cmds[20]; struct dtv_properties cmdseq = { .num = 0, .props = cmds }; /* Clear Q */ static struct dtv_property clear_p[] = { { .cmd = DTV_CLEAR }, }; static struct dtv_properties clear_cmdseq = { .num = 1, .props = clear_p }; if ((ioctl(lfe->lfe_fe_fd, FE_SET_PROPERTY, &clear_cmdseq)) != 0) return -1; /* Tune */ #define S2CMD(c, d)\ cmds[cmdseq.num].cmd = c;\ cmds[cmdseq.num++].u.data = d S2CMD(DTV_DELIVERY_SYSTEM, lm->lm_tuning.dmc_fe_delsys); S2CMD(DTV_FREQUENCY, p->frequency); S2CMD(DTV_INVERSION, p->inversion); /* DVB-T */ if (lfe->lfe_info.type == FE_OFDM) { S2CMD(DTV_BANDWIDTH_HZ, dvb_bandwidth(p->u.ofdm.bandwidth)); S2CMD(DTV_CODE_RATE_HP, p->u.ofdm.code_rate_HP); S2CMD(DTV_CODE_RATE_LP, p->u.ofdm.code_rate_LP); S2CMD(DTV_MODULATION, p->u.ofdm.constellation); S2CMD(DTV_TRANSMISSION_MODE, p->u.ofdm.transmission_mode); S2CMD(DTV_GUARD_INTERVAL, p->u.ofdm.guard_interval); S2CMD(DTV_HIERARCHY, p->u.ofdm.hierarchy_information); /* DVB-C */ } else if (lfe->lfe_info.type == FE_QAM) { S2CMD(DTV_SYMBOL_RATE, p->u.qam.symbol_rate); S2CMD(DTV_MODULATION, p->u.qam.modulation); S2CMD(DTV_INNER_FEC, p->u.qam.fec_inner); /* DVB-S */ } else if (lfe->lfe_info.type == FE_QPSK) { S2CMD(DTV_SYMBOL_RATE, p->u.qpsk.symbol_rate); S2CMD(DTV_INNER_FEC, p->u.qpsk.fec_inner); S2CMD(DTV_MODULATION, dmc->dmc_fe_modulation); S2CMD(DTV_ROLLOFF, dmc->dmc_fe_rolloff); /* ATSC */ } else { S2CMD(DTV_MODULATION, p->u.vsb.modulation); } /* Tune */ S2CMD(DTV_TUNE, 0); #undef S2CMD #endif /* discard stale events */ while (1) { if (ioctl(lfe->lfe_fe_fd, FE_GET_EVENT, &ev) == -1) break; } /* S2 tuning */ #if DVB_API_VERSION >= 5 r = ioctl(lfe->lfe_fe_fd, FE_SET_PROPERTY, &cmdseq); /* v3 tuning */ #else r = ioctl(lfe->lfe_fe_fd, FE_SET_FRONTEND, p); #endif return r; } /* ************************************************************************** * Creation/Config * *************************************************************************/ linuxdvb_frontend_t * linuxdvb_frontend_create0 ( linuxdvb_adapter_t *la, const char *uuid, htsmsg_t *conf, fe_type_t type ) { const char *str; const idclass_t *idc; pthread_t tid; /* Get type */ if (conf) { if (!(str = htsmsg_get_str(conf, "type"))) return NULL; type = dvb_str2type(str); } /* Class */ if (type == FE_QPSK) idc = &linuxdvb_frontend_dvbs_class; else if (type == FE_QAM) idc = &linuxdvb_frontend_dvbc_class; else if (type == FE_OFDM) idc = &linuxdvb_frontend_dvbt_class; else if (type == FE_ATSC) idc = &linuxdvb_frontend_atsc_class; else { tvherror("linuxdvb", "unknown FE type %d", type); return NULL; } linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*) mpegts_input_create0(calloc(1, sizeof(linuxdvb_frontend_t)), idc, uuid, conf); lfe->lfe_info.type = type; /* Input callbacks */ lfe->mi_is_enabled = linuxdvb_frontend_is_enabled; lfe->mi_display_name = linuxdvb_frontend_display_name; lfe->mi_start_mux = linuxdvb_frontend_start_mux; lfe->mi_stop_mux = linuxdvb_frontend_stop_mux; lfe->mi_open_service = linuxdvb_frontend_open_service; lfe->mi_close_service = linuxdvb_frontend_close_service; lfe->lfe_open_pid = linuxdvb_frontend_open_pid; /* Adapter link */ lfe->lh_parent = (linuxdvb_hardware_t*)la; LIST_INSERT_HEAD(&la->lh_children, (linuxdvb_hardware_t*)lfe, lh_parent_link); /* Start table thread */ pthread_create(&tid, NULL, mpegts_input_table_thread, lfe); /* No conf */ if (!conf) return lfe; /* TODO: this should be done differently */ if ((str = htsmsg_get_str(conf, "network"))) { linuxdvb_network_t *ln = linuxdvb_network_find_by_uuid(str); if (ln) { if (ln->ln_type == lfe->lfe_info.type) { mpegts_network_add_input((mpegts_network_t*)ln, (mpegts_input_t*)lfe); } else tvhlog(LOG_WARNING, "linuxdvb", "attempt to add network %s of wrong type %s to %s (%s)", dvb_type2str(ln->ln_type), ln->mn_network_name, lfe->lh_displayname, dvb_type2str(lfe->lfe_info.type)); } } return lfe; } linuxdvb_frontend_t * linuxdvb_frontend_added ( linuxdvb_adapter_t *la, int fe_num, const char *fe_path, const char *dmx_path, const char *dvr_path, const struct dvb_frontend_info *fe_info ) { linuxdvb_hardware_t *lh; linuxdvb_frontend_t *lfe = NULL; /* Find existing */ LIST_FOREACH(lh, &la->lh_children, lh_parent_link) { lfe = (linuxdvb_frontend_t*)lh; if (lfe->lfe_number == fe_num) { if (lfe->lfe_info.type != fe_info->type) { tvhlog(LOG_ERR, "linuxdvb", "detected incorrect fe_type %s != %s", dvb_type2str(lfe->lfe_info.type), dvb_type2str(fe_info->type)); return NULL; } break; } } /* Create new */ if (!lfe) { if (!(lfe = linuxdvb_frontend_create0(la, NULL, NULL, fe_info->type))) { tvhlog(LOG_ERR, "linuxdvb", "failed to create frontend"); return NULL; } } /* Copy info */ lfe->lfe_number = fe_num; memcpy(&lfe->lfe_info, fe_info, sizeof(struct dvb_frontend_info)); /* Set paths */ lfe->lfe_fe_path = strdup(fe_path); lfe->lfe_dmx_path = strdup(dmx_path); lfe->lfe_dvr_path = strdup(dvr_path); return lfe; } void linuxdvb_frontend_save ( linuxdvb_frontend_t *lfe, htsmsg_t *m ) { mpegts_input_save((mpegts_input_t*)lfe, m); htsmsg_add_str(m, "type", dvb_type2str(lfe->lfe_info.type)); } /****************************************************************************** * Editor Configuration * * vim:sts=2:ts=2:sw=2:et *****************************************************************************/