1469 lines
44 KiB
C
1469 lines
44 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "tvheadend.h"
|
|
#include "linuxdvb_private.h"
|
|
#include "notify.h"
|
|
#include "atomic.h"
|
|
#include "tvhpoll.h"
|
|
#include "streaming.h"
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <linux/dvb/dmx.h>
|
|
#include <linux/dvb/frontend.h>
|
|
|
|
|
|
static void
|
|
linuxdvb_frontend_monitor ( void *aux );
|
|
static void *
|
|
linuxdvb_frontend_input_thread ( void *aux );
|
|
|
|
/* **************************************************************************
|
|
* Class definition
|
|
* *************************************************************************/
|
|
|
|
static void
|
|
linuxdvb_frontend_class_save ( idnode_t *in )
|
|
{
|
|
linuxdvb_adapter_t *la = ((linuxdvb_frontend_t*)in)->lfe_adapter;
|
|
linuxdvb_adapter_save(la);
|
|
}
|
|
|
|
const idclass_t linuxdvb_frontend_class =
|
|
{
|
|
.ic_super = &mpegts_input_class,
|
|
.ic_class = "linuxdvb_frontend",
|
|
.ic_caption = "Linux DVB Frontend",
|
|
.ic_save = linuxdvb_frontend_class_save,
|
|
.ic_properties = (const property_t[]) {
|
|
{
|
|
.type = PT_STR,
|
|
.id = "fe_path",
|
|
.name = "Frontend Path",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.off = offsetof(linuxdvb_frontend_t, lfe_fe_path),
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "dvr_path",
|
|
.name = "Input Path",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.off = offsetof(linuxdvb_frontend_t, lfe_dvr_path),
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "dmx_path",
|
|
.name = "Demux Path",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.off = offsetof(linuxdvb_frontend_t, lfe_dmx_path),
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "fe_number",
|
|
.name = "Frontend Number",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.off = offsetof(linuxdvb_frontend_t, lfe_number),
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "powersave",
|
|
.name = "Power Save",
|
|
.off = offsetof(linuxdvb_frontend_t, lfe_powersave),
|
|
},
|
|
{
|
|
.type = PT_U32,
|
|
.id = "skip_bytes",
|
|
.name = "Skip Initial Bytes",
|
|
.opts = PO_ADVANCED,
|
|
.off = offsetof(linuxdvb_frontend_t, lfe_skip_bytes),
|
|
},
|
|
{}
|
|
}
|
|
};
|
|
|
|
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[]){
|
|
{}
|
|
}
|
|
};
|
|
|
|
static idnode_set_t *
|
|
linuxdvb_frontend_dvbs_class_get_childs ( idnode_t *self )
|
|
{
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)self;
|
|
idnode_set_t *is = idnode_set_create(0);
|
|
idnode_set_add(is, &lfe->lfe_satconf->ls_id, NULL);
|
|
return is;
|
|
}
|
|
|
|
static int
|
|
linuxdvb_frontend_dvbs_class_satconf_set ( void *self, const void *str )
|
|
{
|
|
linuxdvb_frontend_t *lfe = self;
|
|
if (lfe->lfe_satconf) {
|
|
if (!strcmp(str ?: "", lfe->lfe_satconf->ls_type))
|
|
return 0;
|
|
linuxdvb_satconf_delete(lfe->lfe_satconf, 1);
|
|
}
|
|
lfe->lfe_satconf = linuxdvb_satconf_create(lfe, str, NULL, NULL);
|
|
return 1;
|
|
}
|
|
|
|
static const void *
|
|
linuxdvb_frontend_dvbs_class_satconf_get ( void *self )
|
|
{
|
|
static const char *s;
|
|
linuxdvb_frontend_t *lfe = self;
|
|
if (lfe->lfe_satconf)
|
|
s = lfe->lfe_satconf->ls_type;
|
|
else
|
|
s = NULL;
|
|
return &s;
|
|
}
|
|
|
|
const idclass_t linuxdvb_frontend_dvbs_class =
|
|
{
|
|
.ic_super = &linuxdvb_frontend_class,
|
|
.ic_class = "linuxdvb_frontend_dvbs",
|
|
.ic_caption = "Linux DVB-S Frontend",
|
|
.ic_get_childs = linuxdvb_frontend_dvbs_class_get_childs,
|
|
.ic_properties = (const property_t[]){
|
|
{
|
|
.type = PT_STR,
|
|
.id = "satconf",
|
|
.name = "SatConfig",
|
|
.opts = PO_NOSAVE,
|
|
.set = linuxdvb_frontend_dvbs_class_satconf_set,
|
|
.get = linuxdvb_frontend_dvbs_class_satconf_get,
|
|
.list = linuxdvb_satconf_type_list,
|
|
.def.s = "simple"
|
|
},
|
|
{
|
|
.id = "networks",
|
|
.type = PT_NONE,
|
|
},
|
|
{}
|
|
}
|
|
};
|
|
|
|
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 void
|
|
linuxdvb_frontend_enabled_updated ( mpegts_input_t *mi )
|
|
{
|
|
char buf[512];
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
|
|
|
|
mi->mi_display_name(mi, buf, sizeof(buf));
|
|
|
|
/* Ensure disabled */
|
|
if (!mi->mi_enabled) {
|
|
tvhtrace("linuxdvb", "%s - disabling tuner", buf);
|
|
if (lfe->lfe_fe_fd > 0) {
|
|
close(lfe->lfe_fe_fd);
|
|
lfe->lfe_fe_fd = -1;
|
|
}
|
|
gtimer_disarm(&lfe->lfe_monitor_timer);
|
|
|
|
/* Ensure FE opened (if not powersave) */
|
|
} else if (!lfe->lfe_powersave && lfe->lfe_fe_fd <= 0 && lfe->lfe_fe_path) {
|
|
lfe->lfe_fe_fd = tvh_open(lfe->lfe_fe_path, O_RDWR | O_NONBLOCK, 0);
|
|
tvhtrace("linuxdvb", "%s - opening FE %s (%d)",
|
|
buf, lfe->lfe_fe_path, lfe->lfe_fe_fd);
|
|
}
|
|
}
|
|
|
|
static int
|
|
linuxdvb_frontend_is_free ( mpegts_input_t *mi )
|
|
{
|
|
linuxdvb_adapter_t *la = ((linuxdvb_frontend_t*)mi)->lfe_adapter;
|
|
linuxdvb_frontend_t *lfe;
|
|
LIST_FOREACH(lfe, &la->la_frontends, lfe_link)
|
|
if (!mpegts_input_is_free((mpegts_input_t*)lfe))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
linuxdvb_frontend_get_weight ( mpegts_input_t *mi, int flags )
|
|
{
|
|
int weight = 0;
|
|
linuxdvb_adapter_t *la = ((linuxdvb_frontend_t*)mi)->lfe_adapter;
|
|
linuxdvb_frontend_t *lfe;
|
|
LIST_FOREACH(lfe, &la->la_frontends, lfe_link)
|
|
weight = MAX(weight, mpegts_input_get_weight((mpegts_input_t*)lfe, flags));
|
|
return weight;
|
|
}
|
|
|
|
static int
|
|
linuxdvb_frontend_get_priority ( mpegts_input_t *mi, mpegts_mux_t *mm, int flags )
|
|
{
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
|
|
int r = mpegts_input_get_priority(mi, mm, flags);
|
|
if (lfe->lfe_satconf)
|
|
r += linuxdvb_satconf_get_priority(lfe->lfe_satconf, mm);
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
linuxdvb_frontend_get_grace ( mpegts_input_t *mi, mpegts_mux_t *mm )
|
|
{
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
|
|
int r = 5;
|
|
if (lfe->lfe_satconf)
|
|
r = linuxdvb_satconf_get_grace(lfe->lfe_satconf, mm);
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
linuxdvb_frontend_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm, int flags )
|
|
{
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
|
|
if (lfe->lfe_fe_path == NULL) return 0;
|
|
if (!mpegts_input_is_enabled(mi, mm, flags)) return 0;
|
|
if (access(lfe->lfe_fe_path, R_OK | W_OK)) return 0;
|
|
if (lfe->lfe_in_setup) return 0;
|
|
return 1;
|
|
}
|
|
|
|
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));
|
|
mpegts_mux_nice_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_ready = 0;
|
|
lfe->lfe_locked = 0;
|
|
lfe->lfe_status = 0;
|
|
|
|
/* Ensure it won't happen immediately */
|
|
gtimer_arm(&lfe->lfe_monitor_timer, linuxdvb_frontend_monitor, lfe, 2);
|
|
|
|
if (lfe->lfe_satconf)
|
|
linuxdvb_satconf_post_stop_mux(lfe->lfe_satconf);
|
|
|
|
lfe->lfe_in_setup = 0;
|
|
}
|
|
|
|
static int
|
|
linuxdvb_frontend_start_mux
|
|
( mpegts_input_t *mi, mpegts_mux_instance_t *mmi )
|
|
{
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
|
|
int res;
|
|
|
|
lfe->lfe_in_setup = 1;
|
|
lfe->lfe_ioctls = 0;
|
|
if (lfe->lfe_satconf)
|
|
res = linuxdvb_satconf_start_mux(lfe->lfe_satconf, mmi);
|
|
else
|
|
res = linuxdvb_frontend_tune1((linuxdvb_frontend_t*)mi, mmi, -1);
|
|
if (res)
|
|
lfe->lfe_in_setup = 0;
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
linuxdvb_frontend_open_pid0
|
|
( linuxdvb_frontend_t *lfe, mpegts_pid_t *mp )
|
|
{
|
|
char name[256];
|
|
struct dmx_pes_filter_params dmx_param;
|
|
int fd;
|
|
|
|
/* Already opened */
|
|
if (mp->mp_fd != -1)
|
|
return;
|
|
|
|
/* Not locked */
|
|
if (!lfe->lfe_locked)
|
|
return;
|
|
|
|
lfe->mi_display_name((mpegts_input_t*)lfe, name, sizeof(name));
|
|
|
|
/* Open DMX */
|
|
fd = tvh_open(lfe->lfe_dmx_path, O_RDWR, 0);
|
|
if(fd == -1) {
|
|
tvherror("linuxdvb", "%s - failed to open dmx for pid %d [e=%s]",
|
|
name, mp->mp_pid, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
/* Install filter */
|
|
tvhtrace("linuxdvb", "%s - open PID %04X (%d) fd %d",
|
|
name, mp->mp_pid, mp->mp_pid, fd);
|
|
memset(&dmx_param, 0, sizeof(dmx_param));
|
|
dmx_param.pid = mp->mp_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, mp->mp_pid, strerror(errno));
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
/* Store */
|
|
mp->mp_fd = fd;
|
|
|
|
return;
|
|
}
|
|
|
|
static mpegts_pid_t *
|
|
linuxdvb_frontend_open_pid
|
|
( mpegts_input_t *mi, mpegts_mux_t *mm, int pid, int type, void *owner )
|
|
{
|
|
mpegts_pid_t *mp;
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
|
|
|
|
if (!(mp = mpegts_input_open_pid(mi, mm, pid, type, owner)))
|
|
return NULL;
|
|
|
|
linuxdvb_frontend_open_pid0(lfe, mp);
|
|
|
|
return mp;
|
|
}
|
|
|
|
static idnode_set_t *
|
|
linuxdvb_frontend_network_list ( mpegts_input_t *mi )
|
|
{
|
|
linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi;
|
|
const idclass_t *idc;
|
|
|
|
if (lfe->lfe_type == DVB_TYPE_T)
|
|
idc = &dvb_network_dvbt_class;
|
|
else if (lfe->lfe_type == DVB_TYPE_C)
|
|
idc = &dvb_network_dvbc_class;
|
|
else if (lfe->lfe_type == DVB_TYPE_S)
|
|
idc = &dvb_network_dvbs_class;
|
|
else if (lfe->lfe_type == DVB_TYPE_ATSC)
|
|
idc = &dvb_network_atsc_class;
|
|
else
|
|
return NULL;
|
|
|
|
return idnode_find_all(idc, NULL);
|
|
}
|
|
|
|
/* **************************************************************************
|
|
* Data processing
|
|
* *************************************************************************/
|
|
|
|
static void
|
|
linuxdvb_frontend_default_tables
|
|
( linuxdvb_frontend_t *lfe, dvb_mux_t *lm )
|
|
{
|
|
mpegts_mux_t *mm = (mpegts_mux_t*)lm;
|
|
|
|
psi_tables_default(mm);
|
|
|
|
/* ATSC */
|
|
if (lfe->lfe_type == DVB_TYPE_ATSC) {
|
|
if (lm->lm_tuning.dmc_fe_modulation == DVB_MOD_VSB_8)
|
|
psi_tables_atsc_t(mm);
|
|
else
|
|
psi_tables_atsc_c(mm);
|
|
|
|
/* DVB */
|
|
} else {
|
|
psi_tables_dvb(mm);
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
ioctl_check( linuxdvb_frontend_t *lfe, int bit )
|
|
{
|
|
return !(lfe->lfe_ioctls & (1 << bit));
|
|
}
|
|
|
|
static inline void
|
|
ioctl_bad( linuxdvb_frontend_t *lfe, int bit )
|
|
{
|
|
lfe->lfe_ioctls |= 1 << bit;
|
|
}
|
|
|
|
static void
|
|
linuxdvb_frontend_monitor ( void *aux )
|
|
{
|
|
uint16_t u16;
|
|
uint32_t u32;
|
|
char buf[256];
|
|
linuxdvb_frontend_t *lfe = aux;
|
|
mpegts_mux_instance_t *mmi = LIST_FIRST(&lfe->mi_mux_active);
|
|
mpegts_mux_t *mm;
|
|
mpegts_pid_t *mp;
|
|
fe_status_t fe_status;
|
|
signal_state_t status;
|
|
signal_status_t sigstat;
|
|
streaming_message_t sm;
|
|
service_t *s;
|
|
int logit = 0;
|
|
#if DVB_VER_ATLEAST(5,10)
|
|
struct dtv_property fe_properties[6];
|
|
struct dtv_properties dtv_prop;
|
|
int gotprop;
|
|
#endif
|
|
|
|
lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf));
|
|
tvhtrace("linuxdvb", "%s - checking FE status%s", buf, lfe->lfe_ready ? " (ready)" : "");
|
|
|
|
/* Disabled */
|
|
if (!lfe->mi_enabled && mmi)
|
|
mmi->mmi_mux->mm_stop(mmi->mmi_mux, 1);
|
|
|
|
/* Close FE */
|
|
if (lfe->lfe_fe_fd > 0 && !mmi && lfe->lfe_powersave) {
|
|
tvhtrace("linuxdvb", "%s - closing frontend", buf);
|
|
close(lfe->lfe_fe_fd);
|
|
lfe->lfe_fe_fd = -1;
|
|
}
|
|
|
|
/* Check accessibility */
|
|
if (lfe->lfe_fe_fd <= 0) {
|
|
if (lfe->lfe_fe_path && access(lfe->lfe_fe_path, R_OK | W_OK)) {
|
|
tvherror("linuxdvb", "%s - device is not accessible", buf);
|
|
// TODO: disable device
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Stop timer */
|
|
if (!mmi || !lfe->lfe_ready) return;
|
|
|
|
/* re-arm */
|
|
gtimer_arm(&lfe->lfe_monitor_timer, linuxdvb_frontend_monitor, lfe, 1);
|
|
|
|
/* Get current status */
|
|
if (ioctl(lfe->lfe_fe_fd, FE_READ_STATUS, &fe_status) == -1) {
|
|
tvhwarn("linuxdvb", "%s - FE_READ_STATUS error %s", buf, strerror(errno));
|
|
/* TODO: check error value */
|
|
return;
|
|
|
|
} 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 */
|
|
if (fe_status != lfe->lfe_status) {
|
|
tvhdebug("linuxdvb", "%s - status %7s (%s%s%s%s%s%s)", buf,
|
|
signal2str(status),
|
|
(fe_status & FE_HAS_SIGNAL) ? "SIGNAL" : "",
|
|
(fe_status & FE_HAS_CARRIER) ? " | CARRIER" : "",
|
|
(fe_status & FE_HAS_VITERBI) ? " | VITERBI" : "",
|
|
(fe_status & FE_HAS_SYNC) ? " | SYNC" : "",
|
|
(fe_status & FE_HAS_LOCK) ? " | SIGNAL" : "",
|
|
(fe_status & FE_TIMEDOUT) ? "TIMEOUT" : "");
|
|
} else {
|
|
tvhtrace("linuxdvb", "%s - status %d (%04X)", buf, status, fe_status);
|
|
}
|
|
lfe->lfe_status = fe_status;
|
|
|
|
/* Get current mux */
|
|
mm = mmi->mmi_mux;
|
|
|
|
/* Waiting for lock */
|
|
if (!lfe->lfe_locked) {
|
|
|
|
/* Locked */
|
|
if (status == SIGNAL_GOOD) {
|
|
tvhdebug("linuxdvb", "%s - locked", buf);
|
|
lfe->lfe_locked = 1;
|
|
|
|
/* Start input */
|
|
tvh_pipe(O_NONBLOCK, &lfe->lfe_dvr_pipe);
|
|
pthread_mutex_lock(&lfe->lfe_dvr_lock);
|
|
tvhthread_create(&lfe->lfe_dvr_thread, NULL,
|
|
linuxdvb_frontend_input_thread, lfe);
|
|
pthread_cond_wait(&lfe->lfe_dvr_cond, &lfe->lfe_dvr_lock);
|
|
pthread_mutex_unlock(&lfe->lfe_dvr_lock);
|
|
|
|
/* Table handlers */
|
|
linuxdvb_frontend_default_tables(lfe, (dvb_mux_t*)mm);
|
|
|
|
/* Locked - ensure everything is open */
|
|
pthread_mutex_lock(&lfe->mi_output_lock);
|
|
RB_FOREACH(mp, &mm->mm_pids, mp_link)
|
|
linuxdvb_frontend_open_pid0(lfe, mp);
|
|
pthread_mutex_unlock(&lfe->mi_output_lock);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/* Statistics - New API */
|
|
#if DVB_VER_ATLEAST(5,10)
|
|
memset(&fe_properties, 0, sizeof(fe_properties));
|
|
|
|
/* Signal strength */
|
|
fe_properties[0].cmd = DTV_STAT_SIGNAL_STRENGTH;
|
|
|
|
/* BER */
|
|
fe_properties[1].cmd = DTV_STAT_PRE_ERROR_BIT_COUNT;
|
|
fe_properties[2].cmd = DTV_STAT_PRE_TOTAL_BIT_COUNT;
|
|
|
|
/* SNR */
|
|
fe_properties[3].cmd = DTV_STAT_CNR;
|
|
|
|
/* PER / UNC */
|
|
fe_properties[4].cmd = DTV_STAT_ERROR_BLOCK_COUNT;
|
|
fe_properties[5].cmd = DTV_STAT_TOTAL_BLOCK_COUNT;
|
|
dtv_prop.num = 6;
|
|
dtv_prop.props = fe_properties;
|
|
|
|
logit = tvhlog_limit(&lfe->lfe_status_log, 3600);
|
|
|
|
if(ioctl_check(lfe, 0) && !ioctl(lfe->lfe_fe_fd, FE_GET_PROPERTY, &dtv_prop)) {
|
|
/* Signal strength */
|
|
gotprop = 0;
|
|
if(ioctl_check(lfe, 1) && fe_properties[0].u.st.len > 0) {
|
|
if(fe_properties[0].u.st.stat[0].scale == FE_SCALE_RELATIVE) {
|
|
mmi->mmi_stats.signal_scale = SIGNAL_STATUS_SCALE_RELATIVE;
|
|
mmi->mmi_stats.signal = fe_properties[0].u.st.stat[0].uvalue;
|
|
gotprop = 1;
|
|
}
|
|
else if(fe_properties[0].u.st.stat[0].scale == FE_SCALE_DECIBEL) {
|
|
mmi->mmi_stats.signal_scale = SIGNAL_STATUS_SCALE_DECIBEL;
|
|
mmi->mmi_stats.signal = fe_properties[0].u.st.stat[0].svalue;
|
|
gotprop = 1;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 1);
|
|
mmi->mmi_stats.signal_scale = SIGNAL_STATUS_SCALE_UNKNOWN;
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unhandled signal scale: %d",
|
|
fe_properties[0].u.st.stat[0].scale);
|
|
}
|
|
}
|
|
if(!gotprop && ioctl_check(lfe, 2)) {
|
|
/* try old API */
|
|
if (!ioctl(lfe->lfe_fe_fd, FE_READ_SIGNAL_STRENGTH, &u16)) {
|
|
mmi->mmi_stats.signal_scale = SIGNAL_STATUS_SCALE_RELATIVE;
|
|
mmi->mmi_stats.signal = u16;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 2);
|
|
mmi->mmi_stats.signal_scale = SIGNAL_STATUS_SCALE_UNKNOWN;
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide signal strength value.");
|
|
}
|
|
}
|
|
|
|
/* ERROR_BIT_COUNT */
|
|
gotprop = 0;
|
|
if(ioctl_check(lfe, 3) && fe_properties[1].u.st.len > 0) {
|
|
if(fe_properties[1].u.st.stat[0].scale == FE_SCALE_COUNTER) {
|
|
mmi->mmi_stats.ec_bit = fe_properties[1].u.st.stat[0].uvalue;
|
|
gotprop = 1;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 3);
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unhandled ERROR_BIT_COUNT scale: %d",
|
|
fe_properties[1].u.st.stat[0].scale);
|
|
}
|
|
}
|
|
/* TOTAL_BIT_COUNT */
|
|
if(gotprop && (fe_properties[2].u.st.len > 0)) {
|
|
gotprop = 0;
|
|
if(ioctl_check(lfe, 4) && fe_properties[2].u.st.stat[0].scale == FE_SCALE_COUNTER) {
|
|
mmi->mmi_stats.tc_bit = fe_properties[2].u.st.stat[0].uvalue;
|
|
gotprop = 1;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 4);
|
|
mmi->mmi_stats.ec_bit = 0; /* both values or none */
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unhandled TOTAL_BIT_COUNT scale: %d",
|
|
fe_properties[2].u.st.stat[0].scale);
|
|
}
|
|
}
|
|
if(!gotprop && ioctl_check(lfe, 5)) {
|
|
/* try old API */
|
|
if (!ioctl(lfe->lfe_fe_fd, FE_READ_BER, &u32))
|
|
mmi->mmi_stats.ber = u32;
|
|
else {
|
|
ioctl_bad(lfe, 5);
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide BER value.");
|
|
}
|
|
}
|
|
|
|
/* SNR */
|
|
gotprop = 0;
|
|
if(ioctl_check(lfe, 6) && fe_properties[3].u.st.len > 0) {
|
|
if(fe_properties[3].u.st.stat[0].scale == FE_SCALE_RELATIVE) {
|
|
mmi->mmi_stats.snr_scale = SIGNAL_STATUS_SCALE_RELATIVE;
|
|
mmi->mmi_stats.snr = fe_properties[3].u.st.stat[0].uvalue;
|
|
gotprop = 1;
|
|
}
|
|
else if(fe_properties[3].u.st.stat[0].scale == FE_SCALE_DECIBEL) {
|
|
mmi->mmi_stats.snr_scale = SIGNAL_STATUS_SCALE_DECIBEL;
|
|
mmi->mmi_stats.snr = fe_properties[3].u.st.stat[0].svalue;
|
|
gotprop = 1;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 6);
|
|
mmi->mmi_stats.snr_scale = SIGNAL_STATUS_SCALE_UNKNOWN;
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unhandled SNR scale: %d",
|
|
fe_properties[3].u.st.stat[0].scale);
|
|
}
|
|
}
|
|
if(!gotprop && ioctl_check(lfe, 7)) {
|
|
/* try old API */
|
|
if (!ioctl(lfe->lfe_fe_fd, FE_READ_SNR, &u16)) {
|
|
mmi->mmi_stats.snr_scale = SIGNAL_STATUS_SCALE_RELATIVE;
|
|
mmi->mmi_stats.snr = u16;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 7);
|
|
mmi->mmi_stats.snr_scale = SIGNAL_STATUS_SCALE_UNKNOWN;
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide SNR value.");
|
|
}
|
|
}
|
|
|
|
/* ERROR_BLOCK_COUNT == Uncorrected blocks (UNC) */
|
|
gotprop = 0;
|
|
if(ioctl_check(lfe, 8) && fe_properties[4].u.st.len > 0) {
|
|
if(fe_properties[4].u.st.stat[0].scale == FE_SCALE_COUNTER) {
|
|
mmi->mmi_stats.unc = mmi->mmi_stats.ec_block = fe_properties[4].u.st.stat[0].uvalue;
|
|
gotprop = 1;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 8);
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unhandled ERROR_BLOCK_COUNT scale: %d",
|
|
fe_properties[4].u.st.stat[0].scale);
|
|
}
|
|
}
|
|
|
|
/* TOTAL_BLOCK_COUNT */
|
|
if(gotprop && (fe_properties[5].u.st.len > 0)) {
|
|
gotprop = 0;
|
|
if(ioctl_check(lfe, 9) && fe_properties[5].u.st.stat[0].scale == FE_SCALE_COUNTER) {
|
|
mmi->mmi_stats.tc_block = fe_properties[5].u.st.stat[0].uvalue;
|
|
gotprop = 1;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 9);
|
|
mmi->mmi_stats.ec_block = mmi->mmi_stats.unc = 0; /* both values or none */
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unhandled TOTAL_BLOCK_COUNT scale: %d",
|
|
fe_properties[5].u.st.stat[0].scale);
|
|
}
|
|
}
|
|
if(!gotprop && ioctl_check(lfe, 10)) {
|
|
/* try old API */
|
|
if (!ioctl(lfe->lfe_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &u32)) {
|
|
mmi->mmi_stats.unc = u32;
|
|
gotprop = 1;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 10);
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide UNC value.");
|
|
}
|
|
}
|
|
/* Older API */
|
|
} else
|
|
#endif
|
|
{
|
|
ioctl_bad(lfe, 0);
|
|
if (ioctl_check(lfe, 1) && !ioctl(lfe->lfe_fe_fd, FE_READ_SIGNAL_STRENGTH, &u16)) {
|
|
mmi->mmi_stats.signal_scale = SIGNAL_STATUS_SCALE_RELATIVE;
|
|
mmi->mmi_stats.signal = u16;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 1);
|
|
mmi->mmi_stats.signal_scale = SIGNAL_STATUS_SCALE_UNKNOWN;
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide signal strength value.");
|
|
}
|
|
if (ioctl_check(lfe, 2) && !ioctl(lfe->lfe_fe_fd, FE_READ_BER, &u32))
|
|
mmi->mmi_stats.ber = u32;
|
|
else {
|
|
ioctl_bad(lfe, 2);
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide BER value.");
|
|
}
|
|
if (ioctl_check(lfe, 3) && !ioctl(lfe->lfe_fe_fd, FE_READ_SNR, &u16)) {
|
|
mmi->mmi_stats.snr_scale = SIGNAL_STATUS_SCALE_RELATIVE;
|
|
mmi->mmi_stats.snr = u16;
|
|
}
|
|
else {
|
|
ioctl_bad(lfe, 3);
|
|
mmi->mmi_stats.snr_scale = SIGNAL_STATUS_SCALE_UNKNOWN;
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide SNR value.");
|
|
}
|
|
if (ioctl_check(lfe, 4) && !ioctl(lfe->lfe_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &u32))
|
|
mmi->mmi_stats.unc = u32;
|
|
else {
|
|
ioctl_bad(lfe, 4);
|
|
if (logit)
|
|
tvhlog(LOG_WARNING, "linuxdvb", "Unable to provide UNC value.");
|
|
}
|
|
}
|
|
|
|
/* Send message */
|
|
sigstat.status_text = signal2str(status);
|
|
sigstat.snr = mmi->mmi_stats.snr;
|
|
sigstat.signal = mmi->mmi_stats.signal;
|
|
sigstat.ber = mmi->mmi_stats.ber;
|
|
sigstat.unc = mmi->mmi_stats.unc;
|
|
sigstat.signal_scale = mmi->mmi_stats.signal_scale;
|
|
sigstat.snr_scale = mmi->mmi_stats.snr_scale;
|
|
sigstat.ec_bit = mmi->mmi_stats.ec_bit;
|
|
sigstat.tc_bit = mmi->mmi_stats.tc_bit;
|
|
sigstat.ec_block = mmi->mmi_stats.ec_block;
|
|
sigstat.tc_block = mmi->mmi_stats.tc_block;
|
|
sm.sm_type = SMT_SIGNAL_STATUS;
|
|
sm.sm_data = &sigstat;
|
|
LIST_FOREACH(s, &lfe->mi_transports, s_active_link) {
|
|
pthread_mutex_lock(&s->s_stream_mutex);
|
|
streaming_pad_deliver(&s->s_streaming_pad, streaming_msg_clone(&sm));
|
|
pthread_mutex_unlock(&s->s_stream_mutex);
|
|
}
|
|
}
|
|
|
|
static void *
|
|
linuxdvb_frontend_input_thread ( void *aux )
|
|
{
|
|
linuxdvb_frontend_t *lfe = aux;
|
|
mpegts_mux_instance_t *mmi;
|
|
int dvr = -1;
|
|
char buf[256];
|
|
int nfds;
|
|
tvhpoll_event_t ev[2];
|
|
tvhpoll_t *efd;
|
|
ssize_t n;
|
|
size_t skip = (MIN(lfe->lfe_skip_bytes, 1024*1024) / 188) * 188;
|
|
size_t counter = 0;
|
|
sbuf_t sb;
|
|
|
|
/* Get MMI */
|
|
pthread_mutex_lock(&lfe->lfe_dvr_lock);
|
|
lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf));
|
|
mmi = LIST_FIRST(&lfe->mi_mux_active);
|
|
pthread_cond_signal(&lfe->lfe_dvr_cond);
|
|
pthread_mutex_unlock(&lfe->lfe_dvr_lock);
|
|
if (mmi == NULL) return NULL;
|
|
|
|
/* Open DVR */
|
|
dvr = tvh_open(lfe->lfe_dvr_path, O_RDONLY | O_NONBLOCK, 0);
|
|
if (dvr < 0) {
|
|
tvherror("linuxdvb", "%s - failed to open %s", buf, lfe->lfe_dvr_path);
|
|
return NULL;
|
|
}
|
|
|
|
/* Setup poll */
|
|
efd = tvhpoll_create(2);
|
|
memset(ev, 0, sizeof(ev));
|
|
ev[0].events = TVHPOLL_IN;
|
|
ev[0].fd = ev[0].data.fd = dvr;
|
|
ev[1].events = TVHPOLL_IN;
|
|
ev[1].fd = ev[1].data.fd = lfe->lfe_dvr_pipe.rd;
|
|
tvhpoll_add(efd, ev, 2);
|
|
|
|
/* Allocate memory */
|
|
sbuf_init_fixed(&sb, 18800);
|
|
|
|
/* Read */
|
|
while (tvheadend_running) {
|
|
nfds = tvhpoll_wait(efd, ev, 1, -1);
|
|
if (nfds < 1) continue;
|
|
if (ev[0].data.fd != dvr) break;
|
|
|
|
/* Read */
|
|
if ((n = sbuf_read(&sb, dvr)) < 0) {
|
|
if (ERRNO_AGAIN(errno))
|
|
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;
|
|
}
|
|
|
|
/* Skip the initial bytes */
|
|
if (counter < skip) {
|
|
counter += n;
|
|
if (counter < skip) {
|
|
sbuf_cut(&sb, n);
|
|
} else {
|
|
sbuf_cut(&sb, skip - (counter - n));
|
|
}
|
|
}
|
|
|
|
/* Process */
|
|
mpegts_input_recv_packets((mpegts_input_t*)lfe, mmi, &sb, NULL, NULL);
|
|
}
|
|
|
|
sbuf_free(&sb);
|
|
tvhpoll_destroy(efd);
|
|
close(dvr);
|
|
return NULL;
|
|
}
|
|
|
|
/* **************************************************************************
|
|
* Tuning
|
|
* *************************************************************************/
|
|
|
|
typedef struct linuxdvb_tbl {
|
|
int t; ///< TVH internal value
|
|
int l; ///< LinuxDVB API value
|
|
} linuxdvb_tbl_t;
|
|
|
|
#define TABLE_EOD -1
|
|
|
|
static int
|
|
linuxdvb2tvh ( const char *prefix, linuxdvb_tbl_t *tbl, int key, int defval )
|
|
{
|
|
while (tbl->t != TABLE_EOD) {
|
|
if (tbl->l == key)
|
|
return tbl->t;
|
|
tbl++;
|
|
}
|
|
tvhtrace("linuxdvb", "%s - linuxdvb2tvh failed %d", prefix, key);
|
|
return defval;
|
|
}
|
|
|
|
static int
|
|
tvh2linuxdvb ( const char *prefix, linuxdvb_tbl_t *tbl, int key, int defval )
|
|
{
|
|
while (tbl->t != TABLE_EOD) {
|
|
if (tbl->t == key)
|
|
return tbl->l;
|
|
tbl++;
|
|
}
|
|
tvhtrace("linuxdvb", "%s - tvh2linuxdvb failed %d", prefix, key);
|
|
return defval;
|
|
}
|
|
|
|
#define TOSTR(s) #s
|
|
#define TR(s, t, d) tvh2linuxdvb(TOSTR(s), t, dmc->dmc_fe_##s , d)
|
|
#define TRU(s, t, d) tvh2linuxdvb(TOSTR(s), t, dmc->u.dmc_fe_##s , d)
|
|
|
|
#if DVB_API_VERSION >= 5
|
|
static linuxdvb_tbl_t delsys_tbl[] = {
|
|
{ .t = DVB_SYS_DVBC_ANNEX_B, .l = SYS_DVBC_ANNEX_B },
|
|
#if DVB_VER_ATLEAST(5,6)
|
|
{ .t = DVB_SYS_DVBC_ANNEX_A, .l = SYS_DVBC_ANNEX_A },
|
|
{ .t = DVB_SYS_DVBC_ANNEX_C, .l = SYS_DVBC_ANNEX_C },
|
|
#else
|
|
{ .t = DVB_SYS_DVBC_ANNEX_A, .l = SYS_DVBC_ANNEX_AC },
|
|
{ .t = DVB_SYS_DVBC_ANNEX_C, .l = SYS_DVBC_ANNEX_AC },
|
|
#endif
|
|
{ .t = DVB_SYS_DVBT, .l = SYS_DVBT },
|
|
#if DVB_VER_ATLEAST(5,3)
|
|
{ .t = DVB_SYS_DVBT2, .l = SYS_DVBT2 },
|
|
#endif
|
|
{ .t = DVB_SYS_DVBS, .l = SYS_DVBS },
|
|
{ .t = DVB_SYS_DVBS2, .l = SYS_DVBS2 },
|
|
{ .t = DVB_SYS_DVBH, .l = SYS_DVBH },
|
|
#if DVB_VER_ATLEAST(5,1)
|
|
{ .t = DVB_SYS_DSS, .l = SYS_DSS },
|
|
#endif
|
|
{ .t = DVB_SYS_ISDBT, .l = SYS_ISDBT },
|
|
{ .t = DVB_SYS_ISDBS, .l = SYS_ISDBS },
|
|
{ .t = DVB_SYS_ISDBC, .l = SYS_ISDBC },
|
|
{ .t = DVB_SYS_ATSC, .l = SYS_ATSC },
|
|
{ .t = DVB_SYS_ATSCMH, .l = SYS_ATSCMH },
|
|
#if DVB_VER_ATLEAST(5,7)
|
|
{ .t = DVB_SYS_DTMB, .l = SYS_DTMB },
|
|
#endif
|
|
{ .t = DVB_SYS_CMMB, .l = SYS_CMMB },
|
|
{ .t = DVB_SYS_DAB, .l = SYS_DAB },
|
|
#if DVB_VER_ATLEAST(5,4)
|
|
{ .t = DVB_SYS_TURBO, .l = SYS_TURBO },
|
|
#endif
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
int linuxdvb2tvh_delsys ( int delsys )
|
|
{
|
|
return linuxdvb2tvh("delsys", delsys_tbl, delsys, DVB_SYS_NONE);
|
|
}
|
|
#endif
|
|
|
|
|
|
int
|
|
linuxdvb_frontend_tune0
|
|
( linuxdvb_frontend_t *lfe, mpegts_mux_instance_t *mmi, uint32_t freq )
|
|
{
|
|
static linuxdvb_tbl_t inv_tbl[] = {
|
|
{ .t = DVB_INVERSION_AUTO, .l = INVERSION_AUTO },
|
|
{ .t = DVB_INVERSION_OFF, .l = INVERSION_OFF },
|
|
{ .t = DVB_INVERSION_ON, .l = INVERSION_ON },
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
static linuxdvb_tbl_t bw_tbl[] = {
|
|
{ .t = DVB_BANDWIDTH_AUTO, .l = BANDWIDTH_AUTO },
|
|
#if DVB_VER_ATLEAST(5,3)
|
|
{ .t = DVB_BANDWIDTH_1_712_MHZ, .l = BANDWIDTH_1_712_MHZ },
|
|
{ .t = DVB_BANDWIDTH_5_MHZ, .l = BANDWIDTH_5_MHZ },
|
|
#endif
|
|
{ .t = DVB_BANDWIDTH_6_MHZ, .l = BANDWIDTH_6_MHZ },
|
|
{ .t = DVB_BANDWIDTH_7_MHZ, .l = BANDWIDTH_7_MHZ },
|
|
{ .t = DVB_BANDWIDTH_8_MHZ, .l = BANDWIDTH_8_MHZ },
|
|
#if DVB_VER_ATLEAST(5,3)
|
|
{ .t = DVB_BANDWIDTH_10_MHZ, .l = BANDWIDTH_10_MHZ },
|
|
#endif
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
static linuxdvb_tbl_t fec_tbl[] = {
|
|
{ .t = DVB_FEC_NONE, .l = FEC_NONE },
|
|
{ .t = DVB_FEC_AUTO, .l = FEC_AUTO },
|
|
{ .t = DVB_FEC_1_2, .l = FEC_1_2 },
|
|
{ .t = DVB_FEC_2_3, .l = FEC_2_3 },
|
|
#if DVB_VER_ATLEAST(5,7)
|
|
{ .t = DVB_FEC_2_5, .l = FEC_2_5 },
|
|
#endif
|
|
{ .t = DVB_FEC_3_4, .l = FEC_3_4 },
|
|
#if DVB_VER_ATLEAST(5,0)
|
|
{ .t = DVB_FEC_3_5, .l = FEC_3_5 },
|
|
#endif
|
|
{ .t = DVB_FEC_4_5, .l = FEC_4_5 },
|
|
{ .t = DVB_FEC_5_6, .l = FEC_5_6 },
|
|
{ .t = DVB_FEC_6_7, .l = FEC_6_7 },
|
|
{ .t = DVB_FEC_7_8, .l = FEC_7_8 },
|
|
{ .t = DVB_FEC_8_9, .l = FEC_8_9 },
|
|
#if DVB_VER_ATLEAST(5,0)
|
|
{ .t = DVB_FEC_9_10, .l = FEC_9_10 },
|
|
#endif
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
static linuxdvb_tbl_t mod_tbl[] = {
|
|
{ .t = DVB_MOD_AUTO, .l = QAM_AUTO },
|
|
{ .t = DVB_MOD_QPSK, .l = QPSK },
|
|
{ .t = DVB_MOD_QAM_16, .l = QAM_16 },
|
|
{ .t = DVB_MOD_QAM_32, .l = QAM_32 },
|
|
{ .t = DVB_MOD_QAM_64, .l = QAM_64 },
|
|
{ .t = DVB_MOD_QAM_128, .l = QAM_128 },
|
|
{ .t = DVB_MOD_QAM_256, .l = QAM_256 },
|
|
{ .t = DVB_MOD_QAM_AUTO, .l = QAM_AUTO },
|
|
{ .t = DVB_MOD_VSB_8, .l = VSB_8 },
|
|
{ .t = DVB_MOD_VSB_16, .l = VSB_16 },
|
|
#if DVB_VER_ATLEAST(5,1)
|
|
{ .t = DVB_MOD_PSK_8, .l = PSK_8, },
|
|
{ .t = DVB_MOD_APSK_16, .l = APSK_16 },
|
|
{ .t = DVB_MOD_APSK_32, .l = APSK_32 },
|
|
#endif
|
|
#if DVB_VER_ATLEAST(5,0)
|
|
{ .t = DVB_MOD_DQPSK, .l = DQPSK },
|
|
#endif
|
|
#if DVB_VER_ATLEAST(5,7)
|
|
{ .t = DVB_MOD_QAM_4_NR, .l = QAM_4_NR },
|
|
#endif
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
static linuxdvb_tbl_t trans_tbl[] = {
|
|
{ .t = DVB_TRANSMISSION_MODE_AUTO, .l = TRANSMISSION_MODE_AUTO },
|
|
#if DVB_VER_ATLEAST(5,3)
|
|
{ .t = DVB_TRANSMISSION_MODE_1K, .l = TRANSMISSION_MODE_1K },
|
|
#endif
|
|
{ .t = DVB_TRANSMISSION_MODE_2K, .l = TRANSMISSION_MODE_2K },
|
|
#if DVB_VER_ATLEAST(5,1)
|
|
{ .t = DVB_TRANSMISSION_MODE_4K, .l = TRANSMISSION_MODE_4K },
|
|
#endif
|
|
{ .t = DVB_TRANSMISSION_MODE_8K, .l = TRANSMISSION_MODE_8K },
|
|
#if DVB_VER_ATLEAST(5,3)
|
|
{ .t = DVB_TRANSMISSION_MODE_16K, .l = TRANSMISSION_MODE_16K },
|
|
{ .t = DVB_TRANSMISSION_MODE_32K, .l = TRANSMISSION_MODE_32K },
|
|
#endif
|
|
#if DVB_VER_ATLEAST(5,7)
|
|
{ .t = DVB_TRANSMISSION_MODE_C3780, .l = TRANSMISSION_MODE_C3780 },
|
|
{ .t = DVB_TRANSMISSION_MODE_C1, .l = TRANSMISSION_MODE_C1 },
|
|
#endif
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
static linuxdvb_tbl_t guard_tbl[] = {
|
|
{ .t = DVB_GUARD_INTERVAL_AUTO, .l = GUARD_INTERVAL_AUTO },
|
|
{ .t = DVB_GUARD_INTERVAL_1_4, .l = GUARD_INTERVAL_1_4 },
|
|
{ .t = DVB_GUARD_INTERVAL_1_8, .l = GUARD_INTERVAL_1_8 },
|
|
{ .t = DVB_GUARD_INTERVAL_1_16, .l = GUARD_INTERVAL_1_16 },
|
|
{ .t = DVB_GUARD_INTERVAL_1_32, .l = GUARD_INTERVAL_1_32 },
|
|
#if DVB_VER_ATLEAST(5,3)
|
|
{ .t = DVB_GUARD_INTERVAL_1_128, .l = GUARD_INTERVAL_1_128 },
|
|
{ .t = DVB_GUARD_INTERVAL_19_128, .l = GUARD_INTERVAL_19_128 },
|
|
{ .t = DVB_GUARD_INTERVAL_19_256, .l = GUARD_INTERVAL_19_256 },
|
|
#endif
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
static linuxdvb_tbl_t h_tbl[] = {
|
|
{ .t = DVB_HIERARCHY_NONE, .l = HIERARCHY_NONE },
|
|
{ .t = DVB_HIERARCHY_AUTO, .l = HIERARCHY_AUTO },
|
|
{ .t = DVB_HIERARCHY_1, .l = HIERARCHY_1 },
|
|
{ .t = DVB_HIERARCHY_2, .l = HIERARCHY_2 },
|
|
{ .t = DVB_HIERARCHY_4, .l = HIERARCHY_4 },
|
|
{ .t = TABLE_EOD }
|
|
};
|
|
#if DVB_API_VERSION >= 5
|
|
static linuxdvb_tbl_t pilot_tbl[] = {
|
|
{ .t = DVB_PILOT_AUTO, .l = PILOT_AUTO },
|
|
{ .t = DVB_PILOT_ON, .l = PILOT_ON },
|
|
{ .t = DVB_PILOT_OFF, .l = PILOT_OFF },
|
|
{ .l = TABLE_EOD }
|
|
};
|
|
static linuxdvb_tbl_t rolloff_tbl[] = {
|
|
{ .t = DVB_HIERARCHY_AUTO, .l = ROLLOFF_AUTO },
|
|
{ .t = DVB_ROLLOFF_20, .l = ROLLOFF_20 },
|
|
{ .t = DVB_ROLLOFF_25, .l = ROLLOFF_25 },
|
|
{ .t = DVB_ROLLOFF_35, .l = ROLLOFF_35 },
|
|
{ .t = TABLE_EOD },
|
|
};
|
|
#endif
|
|
int r;
|
|
struct dvb_frontend_event ev;
|
|
char buf1[256];
|
|
dvb_mux_t *lm = (dvb_mux_t*)mmi->mmi_mux;
|
|
dvb_mux_conf_t *dmc;
|
|
struct dvb_frontend_parameters p;
|
|
|
|
/* Open FE */
|
|
lfe->mi_display_name((mpegts_input_t*)lfe, buf1, sizeof(buf1));
|
|
|
|
if (lfe->lfe_fe_fd <= 0) {
|
|
lfe->lfe_fe_fd = tvh_open(lfe->lfe_fe_path, O_RDWR | O_NONBLOCK, 0);
|
|
tvhtrace("linuxdvb", "%s - opening FE %s (%d)", buf1, lfe->lfe_fe_path, lfe->lfe_fe_fd);
|
|
if (lfe->lfe_fe_fd <= 0) {
|
|
return SM_CODE_TUNING_FAILED;
|
|
}
|
|
}
|
|
lfe->lfe_locked = 0;
|
|
lfe->lfe_status = 0;
|
|
|
|
/*
|
|
* copy the universal parameters to the Linux kernel structure
|
|
*/
|
|
|
|
dmc = &lm->lm_tuning;
|
|
#if ENABLE_TRACE
|
|
{
|
|
char buf2[256];
|
|
dvb_mux_conf_str(&lm->lm_tuning, buf2, sizeof(buf2));
|
|
tvhtrace("linuxdvb", "tuner %s tunning to %s (freq %i)", buf1, buf2, freq);
|
|
}
|
|
#endif
|
|
memset(&p, 0, sizeof(p));
|
|
p.frequency = dmc->dmc_fe_freq;
|
|
p.inversion = TR(inversion, inv_tbl, INVERSION_AUTO);
|
|
switch (dmc->dmc_fe_type) {
|
|
case DVB_TYPE_T:
|
|
#define _OFDM(xyz) p.u.ofdm.xyz
|
|
_OFDM(bandwidth) = TRU(ofdm.bandwidth, bw_tbl, BANDWIDTH_AUTO);
|
|
_OFDM(code_rate_HP) = TRU(ofdm.code_rate_HP, fec_tbl, FEC_AUTO);
|
|
_OFDM(code_rate_LP) = TRU(ofdm.code_rate_LP, fec_tbl, FEC_AUTO);
|
|
_OFDM(constellation) = TR(modulation, mod_tbl, QAM_AUTO);
|
|
_OFDM(transmission_mode) = TRU(ofdm.transmission_mode, trans_tbl, TRANSMISSION_MODE_AUTO);
|
|
_OFDM(guard_interval) = TRU(ofdm.guard_interval, guard_tbl, GUARD_INTERVAL_AUTO);
|
|
_OFDM(hierarchy_information)
|
|
= TRU(ofdm.hierarchy_information, h_tbl, HIERARCHY_AUTO);
|
|
#undef _OFDM
|
|
break;
|
|
case DVB_TYPE_C:
|
|
p.u.qam.symbol_rate = dmc->u.dmc_fe_qam.symbol_rate;
|
|
p.u.qam.fec_inner = TRU(qam.fec_inner, fec_tbl, FEC_AUTO);
|
|
p.u.qam.modulation = TR(modulation, mod_tbl, QAM_AUTO);
|
|
break;
|
|
case DVB_TYPE_S:
|
|
p.u.qpsk.symbol_rate = dmc->u.dmc_fe_qpsk.symbol_rate;
|
|
p.u.qpsk.fec_inner = TRU(qpsk.fec_inner, fec_tbl, FEC_AUTO);
|
|
break;
|
|
case DVB_TYPE_ATSC:
|
|
p.u.vsb.modulation = TR(modulation, mod_tbl, QAM_AUTO);
|
|
break;
|
|
default:
|
|
tvherror("linuxdvb", "%s - unknown FE type %d", buf1, dmc->dmc_fe_type);
|
|
return SM_CODE_TUNING_FAILED;
|
|
}
|
|
|
|
if (freq != (uint32_t)-1)
|
|
p.frequency = freq;
|
|
|
|
if (dmc->dmc_fe_type != lfe->lfe_type) {
|
|
tvherror("linuxdvb", "%s - failed to tune [type does not match %i != %i]", buf1, dmc->dmc_fe_type, lfe->lfe_type);
|
|
return SM_CODE_TUNING_FAILED;
|
|
}
|
|
|
|
/* S2 tuning */
|
|
#if DVB_API_VERSION >= 5
|
|
struct dtv_property cmds[20];
|
|
struct dtv_properties cmdseq;
|
|
|
|
/* 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) {
|
|
tvherror("linuxdvb", "%s - DTV_CLEAR failed [e=%s]", buf1, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (freq == (uint32_t)-1)
|
|
freq = p.frequency;
|
|
|
|
memset(&cmdseq, 0, sizeof(cmdseq));
|
|
cmdseq.props = cmds;
|
|
memset(&cmds, 0, sizeof(cmds));
|
|
|
|
/* Tune */
|
|
#define S2CMD(c, d)\
|
|
cmds[cmdseq.num].cmd = c;\
|
|
cmds[cmdseq.num++].u.data = d
|
|
memset(&cmds, 0, sizeof(cmds));
|
|
S2CMD(DTV_DELIVERY_SYSTEM, TR(delsys, delsys_tbl, SYS_UNDEFINED));
|
|
S2CMD(DTV_FREQUENCY, freq);
|
|
S2CMD(DTV_INVERSION, p.inversion);
|
|
|
|
/* DVB-T */
|
|
if (lfe->lfe_type == DVB_TYPE_T) {
|
|
S2CMD(DTV_BANDWIDTH_HZ, dvb_bandwidth(dmc->u.dmc_fe_ofdm.bandwidth));
|
|
#if DVB_VER_ATLEAST(5,1)
|
|
S2CMD(DTV_CODE_RATE_HP, p.u.ofdm.code_rate_HP);
|
|
S2CMD(DTV_CODE_RATE_LP, p.u.ofdm.code_rate_LP);
|
|
#endif
|
|
S2CMD(DTV_MODULATION, p.u.ofdm.constellation);
|
|
#if DVB_VER_ATLEAST(5,1)
|
|
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);
|
|
#endif
|
|
|
|
/* DVB-C */
|
|
} else if (lfe->lfe_type == DVB_TYPE_C) {
|
|
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_type == DVB_TYPE_S) {
|
|
S2CMD(DTV_SYMBOL_RATE, p.u.qpsk.symbol_rate);
|
|
S2CMD(DTV_INNER_FEC, p.u.qpsk.fec_inner);
|
|
S2CMD(DTV_PILOT, TR(pilot, pilot_tbl, PILOT_AUTO));
|
|
S2CMD(DTV_MODULATION, TR(modulation, mod_tbl, QPSK));
|
|
if (lm->lm_tuning.dmc_fe_delsys == DVB_SYS_DVBS) {
|
|
S2CMD(DTV_ROLLOFF, ROLLOFF_35);
|
|
} else {
|
|
S2CMD(DTV_ROLLOFF, TR(rolloff, rolloff_tbl, ROLLOFF_AUTO));
|
|
}
|
|
|
|
/* 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
|
|
#if ENABLE_TRACE
|
|
int i;
|
|
for (i = 0; i < cmdseq.num; i++)
|
|
tvhtrace("linuxdvb", "S2CMD %02u => %u", cmds[i].cmd, cmds[i].u.data);
|
|
#endif
|
|
r = ioctl(lfe->lfe_fe_fd, FE_SET_PROPERTY, &cmdseq);
|
|
|
|
/* v3 tuning */
|
|
#else
|
|
r = ioctl(lfe->lfe_fe_fd, FE_SET_FRONTEND, p);
|
|
#endif
|
|
|
|
/* 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;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int
|
|
linuxdvb_frontend_tune1
|
|
( linuxdvb_frontend_t *lfe, mpegts_mux_instance_t *mmi, uint32_t freq )
|
|
{
|
|
int r;
|
|
char buf1[256], buf2[256];
|
|
|
|
lfe->mi_display_name((mpegts_input_t*)lfe, buf1, sizeof(buf1));
|
|
mpegts_mux_nice_name(mmi->mmi_mux, buf2, sizeof(buf2));
|
|
tvhdebug("linuxdvb", "%s - starting %s", buf1, buf2);
|
|
|
|
/* Tune */
|
|
tvhtrace("linuxdvb", "%s - tuning", buf1);
|
|
r = linuxdvb_frontend_tune0(lfe, mmi, freq);
|
|
|
|
/* Start monitor */
|
|
if (!r) {
|
|
time(&lfe->lfe_monitor);
|
|
lfe->lfe_monitor += 4;
|
|
gtimer_arm_ms(&lfe->lfe_monitor_timer, linuxdvb_frontend_monitor, lfe, 50);
|
|
lfe->lfe_ready = 1;
|
|
}
|
|
|
|
lfe->lfe_in_setup = 0;
|
|
|
|
return r;
|
|
}
|
|
|
|
/* **************************************************************************
|
|
* Creation/Config
|
|
* *************************************************************************/
|
|
|
|
linuxdvb_frontend_t *
|
|
linuxdvb_frontend_create
|
|
( htsmsg_t *conf, linuxdvb_adapter_t *la, int number,
|
|
const char *fe_path, const char *dmx_path, const char *dvr_path,
|
|
dvb_fe_type_t type, const char *name )
|
|
{
|
|
const idclass_t *idc;
|
|
const char *str, *uuid = NULL, *scuuid = NULL, *sctype = NULL;
|
|
char id[12], lname[256];
|
|
linuxdvb_frontend_t *lfe;
|
|
htsmsg_t *scconf = NULL;
|
|
|
|
/* Internal config ID */
|
|
snprintf(id, sizeof(id), "%s #%d", dvb_type2str(type), number);
|
|
if (conf)
|
|
conf = htsmsg_get_map(conf, id);
|
|
if (conf)
|
|
uuid = htsmsg_get_str(conf, "uuid");
|
|
|
|
/* Fudge configuration for old network entry */
|
|
if (conf) {
|
|
if (!htsmsg_get_list(conf, "networks") &&
|
|
(str = htsmsg_get_str(conf, "network"))) {
|
|
htsmsg_t *l = htsmsg_create_list();
|
|
htsmsg_add_str(l, NULL, str);
|
|
htsmsg_add_msg(conf, "networks", l);
|
|
}
|
|
}
|
|
|
|
/* Class */
|
|
if (type == DVB_TYPE_S)
|
|
idc = &linuxdvb_frontend_dvbs_class;
|
|
else if (type == DVB_TYPE_C)
|
|
idc = &linuxdvb_frontend_dvbc_class;
|
|
else if (type == DVB_TYPE_T)
|
|
idc = &linuxdvb_frontend_dvbt_class;
|
|
else if (type == DVB_TYPE_ATSC)
|
|
idc = &linuxdvb_frontend_atsc_class;
|
|
else {
|
|
tvherror("linuxdvb", "unknown FE type %d", type);
|
|
return NULL;
|
|
}
|
|
|
|
/* Create */
|
|
// Note: there is a bit of a chicken/egg issue below, without the
|
|
// correct "fe_type" we cannot set the network (which is done
|
|
// in mpegts_input_create()). So we must set early.
|
|
lfe = calloc(1, sizeof(linuxdvb_frontend_t));
|
|
lfe->lfe_number = number;
|
|
lfe->lfe_type = type;
|
|
strncpy(lfe->lfe_name, name, sizeof(lfe->lfe_name));
|
|
lfe->lfe_name[sizeof(lfe->lfe_name)-1] = '\0';
|
|
lfe = (linuxdvb_frontend_t*)mpegts_input_create0((mpegts_input_t*)lfe, idc, uuid, conf);
|
|
if (!lfe) return NULL;
|
|
|
|
/* Callbacks */
|
|
lfe->mi_is_free = linuxdvb_frontend_is_free;
|
|
lfe->mi_get_weight = linuxdvb_frontend_get_weight;
|
|
lfe->mi_get_priority = linuxdvb_frontend_get_priority;
|
|
lfe->mi_get_grace = linuxdvb_frontend_get_grace;
|
|
|
|
/* Default name */
|
|
if (!lfe->mi_name) {
|
|
snprintf(lname, sizeof(lname), "%s : %s", name, id);
|
|
lfe->mi_name = strdup(lname);
|
|
}
|
|
|
|
/* Set paths */
|
|
lfe->lfe_fe_path = strdup(fe_path);
|
|
lfe->lfe_dmx_path = strdup(dmx_path);
|
|
lfe->lfe_dvr_path = strdup(dvr_path);
|
|
|
|
/* Input callbacks */
|
|
lfe->mi_is_enabled = linuxdvb_frontend_is_enabled;
|
|
lfe->mi_start_mux = linuxdvb_frontend_start_mux;
|
|
lfe->mi_stop_mux = linuxdvb_frontend_stop_mux;
|
|
lfe->mi_network_list = linuxdvb_frontend_network_list;
|
|
lfe->mi_open_pid = linuxdvb_frontend_open_pid;
|
|
lfe->mi_enabled_updated = linuxdvb_frontend_enabled_updated;
|
|
|
|
/* Adapter link */
|
|
lfe->lfe_adapter = la;
|
|
LIST_INSERT_HEAD(&la->la_frontends, lfe, lfe_link);
|
|
|
|
/* DVR lock/cond */
|
|
pthread_mutex_init(&lfe->lfe_dvr_lock, NULL);
|
|
pthread_cond_init(&lfe->lfe_dvr_cond, NULL);
|
|
|
|
/* Satconf */
|
|
if (conf) {
|
|
if ((scconf = htsmsg_get_map(conf, "satconf"))) {
|
|
sctype = htsmsg_get_str(scconf, "type");
|
|
scuuid = htsmsg_get_str(scconf, "uuid");
|
|
}
|
|
}
|
|
|
|
/* Create satconf */
|
|
if (lfe->lfe_type == DVB_TYPE_S && !lfe->lfe_satconf)
|
|
lfe->lfe_satconf = linuxdvb_satconf_create(lfe, sctype, scuuid, scconf);
|
|
|
|
/* Double check enabled */
|
|
linuxdvb_frontend_enabled_updated((mpegts_input_t*)lfe);
|
|
|
|
return lfe;
|
|
}
|
|
|
|
void
|
|
linuxdvb_frontend_save ( linuxdvb_frontend_t *lfe, htsmsg_t *fe )
|
|
{
|
|
char id[12];
|
|
htsmsg_t *m = htsmsg_create_map();
|
|
|
|
/* Save frontend */
|
|
mpegts_input_save((mpegts_input_t*)lfe, m);
|
|
htsmsg_add_str(m, "type", dvb_type2str(lfe->lfe_type));
|
|
if (lfe->lfe_satconf) {
|
|
htsmsg_t *s = htsmsg_create_map();
|
|
linuxdvb_satconf_save(lfe->lfe_satconf, s);
|
|
htsmsg_add_str(s, "uuid", idnode_uuid_as_str(&lfe->lfe_satconf->ls_id));
|
|
htsmsg_add_msg(m, "satconf", s);
|
|
}
|
|
|
|
/* Add to list */
|
|
snprintf(id, sizeof(id), "%s #%d", dvb_type2str(lfe->lfe_type), lfe->lfe_number);
|
|
htsmsg_add_msg(fe, id, m);
|
|
}
|
|
|
|
void
|
|
linuxdvb_frontend_delete ( linuxdvb_frontend_t *lfe )
|
|
{
|
|
lock_assert(&global_lock);
|
|
|
|
/* Ensure we're stopped */
|
|
mpegts_input_stop_all((mpegts_input_t*)lfe);
|
|
|
|
/* Stop monitor */
|
|
gtimer_disarm(&lfe->lfe_monitor_timer);
|
|
|
|
/* Close FDs */
|
|
if (lfe->lfe_fe_fd > 0)
|
|
close(lfe->lfe_fe_fd);
|
|
|
|
/* Remove from adapter */
|
|
LIST_REMOVE(lfe, lfe_link);
|
|
|
|
/* Free memory */
|
|
free(lfe->lfe_fe_path);
|
|
free(lfe->lfe_dmx_path);
|
|
free(lfe->lfe_dvr_path);
|
|
|
|
/* Delete satconf */
|
|
if (lfe->lfe_satconf)
|
|
linuxdvb_satconf_delete(lfe->lfe_satconf, 0);
|
|
|
|
/* Finish */
|
|
mpegts_input_delete((mpegts_input_t*)lfe, 0);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Editor Configuration
|
|
*
|
|
* vim:sts=2:ts=2:sw=2:et
|
|
*****************************************************************************/
|