495 lines
11 KiB
C
495 lines
11 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 <dirent.h>
|
|
|
|
#include <linux/dvb/frontend.h>
|
|
#include <linux/dvb/dmx.h>
|
|
|
|
#include "settings.h"
|
|
|
|
#include "tvheadend.h"
|
|
#include "dvb.h"
|
|
#include "channels.h"
|
|
#include "subscriptions.h"
|
|
#include "psi.h"
|
|
#include "dvb_support.h"
|
|
#include "notify.h"
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
dvb_transport_open_demuxers(th_dvb_adapter_t *tda, service_t *t)
|
|
{
|
|
struct dmx_pes_filter_params dmx_param;
|
|
int fd;
|
|
elementary_stream_t *st;
|
|
|
|
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
|
if(st->es_pid >= 0x2000)
|
|
continue;
|
|
|
|
if(st->es_demuxer_fd != -1)
|
|
continue;
|
|
|
|
fd = tvh_open(tda->tda_demux_path, O_RDWR, 0);
|
|
st->es_cc_valid = 0;
|
|
|
|
if(fd == -1) {
|
|
st->es_demuxer_fd = -1;
|
|
tvhlog(LOG_ERR, "dvb",
|
|
"\"%s\" unable to open demuxer \"%s\" for pid %d -- %s",
|
|
t->s_identifier, tda->tda_demux_path,
|
|
st->es_pid, strerror(errno));
|
|
continue;
|
|
}
|
|
|
|
memset(&dmx_param, 0, sizeof(dmx_param));
|
|
dmx_param.pid = st->es_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)) {
|
|
tvhlog(LOG_ERR, "dvb",
|
|
"\"%s\" unable to configure demuxer \"%s\" for pid %d -- %s",
|
|
t->s_identifier, tda->tda_demux_path,
|
|
st->es_pid, strerror(errno));
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
|
|
st->es_demuxer_fd = fd;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Switch the adapter (which is implicitly tied to our transport)
|
|
* to receive the given transport.
|
|
*
|
|
* But we only do this if 'weight' is higher than all of the current
|
|
* transports that is subscribing to the adapter
|
|
*/
|
|
static int
|
|
dvb_transport_start(service_t *t, unsigned int weight, int force_start)
|
|
{
|
|
int w, r;
|
|
th_dvb_adapter_t *tda = t->s_dvb_mux_instance->tdmi_adapter;
|
|
th_dvb_mux_instance_t *tdmi = tda->tda_mux_current;
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
if(tda->tda_rootpath == NULL)
|
|
return SM_CODE_NO_HW_ATTACHED;
|
|
|
|
if(t->s_dvb_mux_instance && !t->s_dvb_mux_instance->tdmi_enabled)
|
|
return SM_CODE_MUX_NOT_ENABLED; /* Mux is disabled */
|
|
|
|
/* Check if adapter is idle, or already tuned */
|
|
|
|
if(tdmi != NULL &&
|
|
(tdmi != t->s_dvb_mux_instance ||
|
|
tda->tda_hostconnection == HOSTCONNECTION_USB12)) {
|
|
|
|
w = service_compute_weight(&tda->tda_transports);
|
|
if(w && w >= weight && !force_start)
|
|
/* We are outranked by weight, cant use it */
|
|
return SM_CODE_NOT_FREE;
|
|
|
|
dvb_adapter_clean(tda);
|
|
}
|
|
|
|
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
|
|
|
r = dvb_fe_tune(t->s_dvb_mux_instance, "Transport start");
|
|
if(!r)
|
|
LIST_INSERT_HEAD(&tda->tda_transports, t, s_active_link);
|
|
|
|
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
|
|
|
if(!r)
|
|
dvb_transport_open_demuxers(tda, t);
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
dvb_transport_stop(service_t *t)
|
|
{
|
|
th_dvb_adapter_t *tda = t->s_dvb_mux_instance->tdmi_adapter;
|
|
elementary_stream_t *st;
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
|
LIST_REMOVE(t, s_active_link);
|
|
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
|
|
|
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
|
if(st->es_demuxer_fd != -1) {
|
|
close(st->es_demuxer_fd);
|
|
st->es_demuxer_fd = -1;
|
|
}
|
|
}
|
|
t->s_status = SERVICE_IDLE;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
dvb_transport_refresh(service_t *t)
|
|
{
|
|
th_dvb_adapter_t *tda = t->s_dvb_mux_instance->tdmi_adapter;
|
|
|
|
lock_assert(&global_lock);
|
|
dvb_transport_open_demuxers(tda, t);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Load config for the given mux
|
|
*/
|
|
void
|
|
dvb_transport_load(th_dvb_mux_instance_t *tdmi)
|
|
{
|
|
htsmsg_t *l, *c;
|
|
htsmsg_field_t *f;
|
|
uint32_t sid, pmt;
|
|
const char *s;
|
|
unsigned int u32;
|
|
service_t *t;
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
if((l = hts_settings_load("dvbtransports/%s", tdmi->tdmi_identifier)) == NULL)
|
|
return;
|
|
|
|
HTSMSG_FOREACH(f, l) {
|
|
if((c = htsmsg_get_map_by_field(f)) == NULL)
|
|
continue;
|
|
|
|
if(htsmsg_get_u32(c, "service_id", &sid))
|
|
continue;
|
|
|
|
if(htsmsg_get_u32(c, "pmt", &pmt))
|
|
continue;
|
|
|
|
t = dvb_transport_find(tdmi, sid, pmt, f->hmf_name);
|
|
|
|
htsmsg_get_u32(c, "stype", &t->s_servicetype);
|
|
if(htsmsg_get_u32(c, "scrambled", &u32))
|
|
u32 = 0;
|
|
t->s_scrambled = u32;
|
|
|
|
if(htsmsg_get_u32(c, "channel", &u32))
|
|
u32 = 0;
|
|
t->s_channel_number = u32;
|
|
|
|
s = htsmsg_get_str(c, "provider");
|
|
t->s_provider = s ? strdup(s) : NULL;
|
|
|
|
s = htsmsg_get_str(c, "servicename");
|
|
t->s_svcname = s ? strdup(s) : NULL;
|
|
|
|
pthread_mutex_lock(&t->s_stream_mutex);
|
|
service_make_nicename(t);
|
|
psi_load_service_settings(c, t);
|
|
pthread_mutex_unlock(&t->s_stream_mutex);
|
|
|
|
s = htsmsg_get_str(c, "dvb_default_charset");
|
|
t->s_dvb_default_charset = s ? strdup(s) : NULL;
|
|
|
|
if(htsmsg_get_u32(c, "dvb_eit_enable", &u32))
|
|
u32 = 1;
|
|
t->s_dvb_eit_enable = u32;
|
|
|
|
s = htsmsg_get_str(c, "channelname");
|
|
if(htsmsg_get_u32(c, "mapped", &u32))
|
|
u32 = 0;
|
|
|
|
if(s && u32)
|
|
service_map_channel(t, channel_find_by_name(s, 1, 0), 0);
|
|
}
|
|
htsmsg_destroy(l);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
dvb_transport_save(service_t *t)
|
|
{
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
htsmsg_add_u32(m, "service_id", t->s_dvb_service_id);
|
|
htsmsg_add_u32(m, "pmt", t->s_pmt_pid);
|
|
htsmsg_add_u32(m, "stype", t->s_servicetype);
|
|
htsmsg_add_u32(m, "scrambled", t->s_scrambled);
|
|
htsmsg_add_u32(m, "channel", t->s_channel_number);
|
|
|
|
if(t->s_provider != NULL)
|
|
htsmsg_add_str(m, "provider", t->s_provider);
|
|
|
|
if(t->s_svcname != NULL)
|
|
htsmsg_add_str(m, "servicename", t->s_svcname);
|
|
|
|
if(t->s_ch != NULL) {
|
|
htsmsg_add_str(m, "channelname", t->s_ch->ch_name);
|
|
htsmsg_add_u32(m, "mapped", 1);
|
|
}
|
|
|
|
if(t->s_dvb_default_charset != NULL)
|
|
htsmsg_add_str(m, "dvb_default_charset", t->s_dvb_default_charset);
|
|
|
|
htsmsg_add_u32(m, "dvb_eit_enable", t->s_dvb_eit_enable);
|
|
|
|
pthread_mutex_lock(&t->s_stream_mutex);
|
|
psi_save_service_settings(m, t);
|
|
pthread_mutex_unlock(&t->s_stream_mutex);
|
|
|
|
hts_settings_save(m, "dvbtransports/%s/%s",
|
|
t->s_dvb_mux_instance->tdmi_identifier,
|
|
t->s_identifier);
|
|
|
|
htsmsg_destroy(m);
|
|
dvb_transport_notify(t);
|
|
}
|
|
|
|
|
|
/**
|
|
* Called to get quality for the given transport
|
|
*
|
|
* We keep track of this for the entire mux (if we see errors), soo..
|
|
* return that value
|
|
*/
|
|
static int
|
|
dvb_transport_quality(service_t *t)
|
|
{
|
|
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
return tdmi->tdmi_adapter->tda_qmon ? tdmi->tdmi_quality : 100;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate a descriptive name for the source
|
|
*/
|
|
static void
|
|
dvb_transport_setsourceinfo(service_t *t, struct source_info *si)
|
|
{
|
|
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
|
char buf[100];
|
|
|
|
memset(si, 0, sizeof(struct source_info));
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
if(tdmi->tdmi_adapter->tda_rootpath != NULL)
|
|
si->si_device = strdup(tdmi->tdmi_adapter->tda_rootpath);
|
|
|
|
si->si_adapter = strdup(tdmi->tdmi_adapter->tda_displayname);
|
|
|
|
if(tdmi->tdmi_network != NULL)
|
|
si->si_network = strdup(tdmi->tdmi_network);
|
|
|
|
dvb_mux_nicename(buf, sizeof(buf), tdmi);
|
|
si->si_mux = strdup(buf);
|
|
|
|
if(t->s_provider != NULL)
|
|
si->si_provider = strdup(t->s_provider);
|
|
|
|
if(t->s_svcname != NULL)
|
|
si->si_service = strdup(t->s_svcname);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
dvb_grace_period(service_t *t)
|
|
{
|
|
return 10;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find a transport based on 'serviceid' on the given mux
|
|
*
|
|
* If it cannot be found we create it if 'pmt_pid' is also set
|
|
*/
|
|
service_t *
|
|
dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
|
const char *identifier)
|
|
{
|
|
service_t *t;
|
|
char tmp[200];
|
|
char buf[200];
|
|
|
|
lock_assert(&global_lock);
|
|
|
|
LIST_FOREACH(t, &tdmi->tdmi_transports, s_group_link) {
|
|
if(t->s_dvb_service_id == sid)
|
|
return t;
|
|
}
|
|
|
|
if(pmt_pid == 0)
|
|
return NULL;
|
|
|
|
if(identifier == NULL) {
|
|
snprintf(tmp, sizeof(tmp), "%s_%04x", tdmi->tdmi_identifier, sid);
|
|
identifier = tmp;
|
|
}
|
|
|
|
dvb_mux_nicename(buf, sizeof(buf), tdmi);
|
|
tvhlog(LOG_DEBUG, "dvb", "Add service \"%s\" on \"%s\"", identifier, buf);
|
|
|
|
t = service_create(identifier, SERVICE_TYPE_DVB, S_MPEG_TS);
|
|
|
|
t->s_dvb_service_id = sid;
|
|
t->s_pmt_pid = pmt_pid;
|
|
|
|
t->s_start_feed = dvb_transport_start;
|
|
t->s_refresh_feed = dvb_transport_refresh;
|
|
t->s_stop_feed = dvb_transport_stop;
|
|
t->s_config_save = dvb_transport_save;
|
|
t->s_setsourceinfo = dvb_transport_setsourceinfo;
|
|
t->s_quality_index = dvb_transport_quality;
|
|
t->s_grace_period = dvb_grace_period;
|
|
|
|
t->s_dvb_mux_instance = tdmi;
|
|
LIST_INSERT_HEAD(&tdmi->tdmi_transports, t, s_group_link);
|
|
|
|
pthread_mutex_lock(&t->s_stream_mutex);
|
|
service_make_nicename(t);
|
|
pthread_mutex_unlock(&t->s_stream_mutex);
|
|
|
|
dvb_adapter_notify(tdmi->tdmi_adapter);
|
|
return t;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
htsmsg_t *
|
|
dvb_transport_build_msg(service_t *t)
|
|
{
|
|
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
char buf[100];
|
|
|
|
htsmsg_add_str(m, "id", t->s_identifier);
|
|
htsmsg_add_u32(m, "enabled", t->s_enabled);
|
|
htsmsg_add_u32(m, "channel", t->s_channel_number);
|
|
|
|
htsmsg_add_u32(m, "sid", t->s_dvb_service_id);
|
|
htsmsg_add_u32(m, "pmt", t->s_pmt_pid);
|
|
htsmsg_add_u32(m, "pcr", t->s_pcr_pid);
|
|
|
|
htsmsg_add_str(m, "type", service_servicetype_txt(t));
|
|
|
|
htsmsg_add_str(m, "svcname", t->s_svcname ?: "");
|
|
htsmsg_add_str(m, "provider", t->s_provider ?: "");
|
|
|
|
htsmsg_add_str(m, "network", tdmi->tdmi_network ?: "");
|
|
|
|
dvb_mux_nicefreq(buf, sizeof(buf), tdmi);
|
|
htsmsg_add_str(m, "mux", buf);
|
|
|
|
if(t->s_ch != NULL)
|
|
htsmsg_add_str(m, "channelname", t->s_ch->ch_name);
|
|
|
|
if(t->s_dvb_default_charset != NULL)
|
|
htsmsg_add_str(m, "dvb_default_charset", t->s_dvb_default_charset);
|
|
|
|
htsmsg_add_u32(m, "dvb_eit_enable", t->s_dvb_eit_enable);
|
|
|
|
return m;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
dvb_transport_notify_by_adapter(th_dvb_adapter_t *tda)
|
|
{
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
htsmsg_add_str(m, "adapterId", tda->tda_identifier);
|
|
notify_by_msg("dvbService", m);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
dvb_transport_notify(service_t *t)
|
|
{
|
|
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
|
|
htsmsg_add_str(m, "adapterId", tdmi->tdmi_adapter->tda_identifier);
|
|
notify_by_msg("dvbService", m);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the signal status from a DVB transport
|
|
*/
|
|
int
|
|
dvb_transport_get_signal_status(service_t *t, signal_status_t *status)
|
|
{
|
|
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
|
|
|
status->status_text = dvb_mux_status(tdmi);
|
|
status->snr = tdmi->tdmi_snr;
|
|
status->signal = tdmi->tdmi_signal;
|
|
status->ber = tdmi->tdmi_ber;
|
|
status->unc = tdmi->tdmi_uncorrected_blocks;
|
|
return 0;
|
|
}
|