/*
* Tvheadend - Linux DVB Multiplex
*
* 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 "input.h"
#include "linuxdvb_private.h"
#include "queue.h"
#include "settings.h"
#include
#include
#include
#include
#include
#include
#include
/* **************************************************************************
* Class definition
* *************************************************************************/
static void
linuxdvb_mux_delete ( mpegts_mux_t *mm, int delconf );
extern const idclass_t mpegts_mux_class;
/*
* Generic
*/
/* Macro to defien mux class str get/set */
#define linuxdvb_mux_class_X(c, f, p, l, ...)\
static const void * \
linuxdvb_mux_##c##_class_##l##_get (void *o)\
{\
static const char *s;\
linuxdvb_mux_t *lm = o;\
s = dvb_##l##2str(lm->lm_tuning.dmc_fe_params.u.f.p);\
return &s;\
}\
static int \
linuxdvb_mux_##c##_class_##l##_set (void *o, const void *v)\
{\
linuxdvb_mux_t *lm = o;\
lm->lm_tuning.dmc_fe_params.u.f.p = dvb_str2##l ((const char*)v);\
return 1;\
}\
static htsmsg_t *\
linuxdvb_mux_##c##_class_##l##_enum (void *o)\
{\
static const int t[] = { __VA_ARGS__ };\
int i;\
htsmsg_t *m = htsmsg_create_list();\
for (i = 0; i < ARRAY_SIZE(t); i++)\
htsmsg_add_str(m, NULL, dvb_##l##2str(t[i]));\
return m;\
}
#define MUX_PROP_STR(_id, _name, t, l, d)\
.type = PT_STR,\
.id = _id,\
.name = _name,\
.get = linuxdvb_mux_##t##_class_##l##_get,\
.set = linuxdvb_mux_##t##_class_##l##_set,\
.list = linuxdvb_mux_##t##_class_##l##_enum,\
.def.s = d
static const void *
linuxdvb_mux_class_delsys_get (void *o)
{
static const char *s;
#if DVB_VER_ATLEAST(5,0)
linuxdvb_mux_t *lm = o;
s = dvb_delsys2str(lm->lm_tuning.dmc_fe_delsys);
#else
s = NULL;
#endif
return &s;
}
static int
linuxdvb_mux_class_delsys_set (void *o, const void *v)
{
#if DVB_VER_ATLEAST(5,0)
const char *s = v;
int delsys = dvb_str2delsys(s);
linuxdvb_mux_t *lm = o;
if (delsys != lm->lm_tuning.dmc_fe_delsys) {
lm->lm_tuning.dmc_fe_delsys = dvb_str2delsys(s);
return 1;
}
#endif
return 0;
}
const idclass_t linuxdvb_mux_class =
{
.ic_super = &mpegts_mux_class,
.ic_class = "linuxdvb_mux",
.ic_caption = "Linux DVB Multiplex",
.ic_properties = (const property_t[]){
{}
}
};
/*
* DVB-T
*/
linuxdvb_mux_class_X(dvbt, ofdm, bandwidth, bw,
BANDWIDTH_AUTO
, BANDWIDTH_8_MHZ, BANDWIDTH_7_MHZ, BANDWIDTH_6_MHZ
#if DVB_VER_ATLEAST(5,4)
, BANDWIDTH_5_MHZ
, BANDWIDTH_10_MHZ
, BANDWIDTH_1_712_MHZ
#endif
);
linuxdvb_mux_class_X(dvbt, ofdm, constellation, qam,
QAM_AUTO, QPSK, QAM_16, QAM_64, QAM_256
);
linuxdvb_mux_class_X(dvbt, ofdm, transmission_mode, mode,
TRANSMISSION_MODE_AUTO,
TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K
#if DVB_VER_ATLEAST(5,4)
, TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K
, TRANSMISSION_MODE_32K
#endif
);
linuxdvb_mux_class_X(dvbt, ofdm, guard_interval, guard,
GUARD_INTERVAL_AUTO, GUARD_INTERVAL_1_4,
GUARD_INTERVAL_1_8, GUARD_INTERVAL_1_16,
GUARD_INTERVAL_1_32
#if DVB_VER_ATLEAST(5,4)
, GUARD_INTERVAL_1_128, GUARD_INTERVAL_19_128
, GUARD_INTERVAL_19_256
#endif
);
linuxdvb_mux_class_X(dvbt, ofdm, hierarchy_information, hier,
HIERARCHY_AUTO, HIERARCHY_NONE,
HIERARCHY_1, HIERARCHY_2, HIERARCHY_4
);
linuxdvb_mux_class_X(dvbt, ofdm, code_rate_HP, fechi,
FEC_AUTO,
FEC_1_2, FEC_2_3, FEC_3_4, FEC_4_5, FEC_5_6, FEC_7_8
#if DVB_VER_ATLEAST(5,4)
, FEC_3_5
#endif
);
linuxdvb_mux_class_X(dvbt, ofdm, code_rate_LP, feclo,
FEC_AUTO,
FEC_1_2, FEC_2_3, FEC_3_4, FEC_4_5, FEC_5_6, FEC_7_8
#if DVB_VER_ATLEAST(5,4)
, FEC_3_5
#endif
);
#define linuxdvb_mux_dvbt_class_delsys_get linuxdvb_mux_class_delsys_get
#define linuxdvb_mux_dvbt_class_delsys_set linuxdvb_mux_class_delsys_set
static htsmsg_t *
linuxdvb_mux_dvbt_class_delsys_enum (void *o)
{
htsmsg_t *list = htsmsg_create_list();
#if DVB_VER_ATLEAST(5,0)
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_DVBT));
#endif
#if DVB_VER_ATLEAST(5,4)
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_DVBT2));
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_TURBO));
#endif
return list;
}
const idclass_t linuxdvb_mux_dvbt_class =
{
.ic_super = &linuxdvb_mux_class,
.ic_class = "linuxdvb_mux_dvbt",
.ic_caption = "Linux DVB-T Multiplex",
.ic_properties = (const property_t[]){
{
MUX_PROP_STR("delsys", "Delivery System", dvbt, delsys, "DVBT"),
},
{
.type = PT_U32,
.id = "frequency",
.name = "Frequency (Hz)",
.opts = PO_WRONCE,
.off = offsetof(linuxdvb_mux_t, lm_tuning.dmc_fe_params.frequency),
},
{
MUX_PROP_STR("bandwidth", "Bandwidth", dvbt, bw, "AUTO")
},
{
MUX_PROP_STR("constellation", "Constellation", dvbt, qam, "AUTO")
},
{
MUX_PROP_STR("transmission_mode", "Transmission Mode", dvbt, mode, "AUTO")
},
{
MUX_PROP_STR("guard_interval", "Guard Interval", dvbt, guard, "AUTO")
},
{
MUX_PROP_STR("hierarchy", "Hierarchy", dvbt, hier, "AUTO"),
},
{
MUX_PROP_STR("fec_hi", "FEC High", dvbt, fechi, "AUTO"),
},
{
MUX_PROP_STR("fec_lo", "FEC Low", dvbt, feclo, "AUTO"),
},
{}
}
};
/*
* DVB-C
*/
linuxdvb_mux_class_X(dvbc, qam, modulation, qam,
QAM_AUTO, QAM_16, QAM_32, QAM_64, QAM_128, QAM_256
);
linuxdvb_mux_class_X(dvbc, qam, fec_inner, fec,
FEC_AUTO, FEC_NONE,
FEC_1_2, FEC_2_3, FEC_3_4, FEC_4_5, FEC_5_6, FEC_8_9
#if DVB_VER_ATLEAST(5,4)
, FEC_9_10
#endif
);
#define linuxdvb_mux_dvbc_class_delsys_get linuxdvb_mux_class_delsys_get
#define linuxdvb_mux_dvbc_class_delsys_set linuxdvb_mux_class_delsys_set
static htsmsg_t *
linuxdvb_mux_dvbc_class_delsys_enum (void *o)
{
htsmsg_t *list = htsmsg_create_list();
#if DVB_VER_ATLEAST(5,0)
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_DVBC_ANNEX_AC));
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_DVBC_ANNEX_B));
#endif
return list;
}
const idclass_t linuxdvb_mux_dvbc_class =
{
.ic_super = &linuxdvb_mux_class,
.ic_class = "linuxdvb_mux_dvbc",
.ic_caption = "Linux DVB-C Multiplex",
.ic_properties = (const property_t[]){
{
MUX_PROP_STR("delsys", "Delivery System", dvbc, delsys, "DVBC_ANNEX_AC"),
},
{
.type = PT_U32,
.id = "frequency",
.name = "Frequency (Hz)",
.opts = PO_WRONCE,
.off = offsetof(linuxdvb_mux_t, lm_tuning.dmc_fe_params.frequency),
},
{
.type = PT_U32,
.id = "symbolrate",
.name = "Symbol Rate (Sym/s)",
.opts = PO_WRONCE,
.off = offsetof(linuxdvb_mux_t, lm_tuning.dmc_fe_params.u.qam.symbol_rate),
},
{
MUX_PROP_STR("constellation", "Constellation", dvbc, qam, "AUTO")
},
{
MUX_PROP_STR("fec", "FEC", dvbc, fec, "AUTO")
},
{}
}
};
linuxdvb_mux_class_X(dvbs, qpsk, fec_inner, fec,
FEC_AUTO, FEC_NONE,
FEC_1_2, FEC_2_3, FEC_3_4, FEC_4_5, FEC_5_6, FEC_7_8,
FEC_8_9
#if DVB_VER_ATLEAST(5,4)
, FEC_3_5, FEC_9_10
#endif
);
static const void *
linuxdvb_mux_dvbs_class_polarity_get (void *o)
{
static const char *s;
linuxdvb_mux_t *lm = o;
s = dvb_pol2str(lm->lm_tuning.dmc_fe_polarisation);
return &s;
}
static int
linuxdvb_mux_dvbs_class_polarity_set (void *o, const void *s)
{
linuxdvb_mux_t *lm = o;
lm->lm_tuning.dmc_fe_polarisation = dvb_str2pol((const char*)s);
return 1;
}
static htsmsg_t *
linuxdvb_mux_dvbs_class_polarity_enum (void *o)
{
htsmsg_t *list = htsmsg_create_list();
htsmsg_add_str(list, NULL, dvb_pol2str(POLARISATION_VERTICAL));
htsmsg_add_str(list, NULL, dvb_pol2str(POLARISATION_HORIZONTAL));
htsmsg_add_str(list, NULL, dvb_pol2str(POLARISATION_CIRCULAR_LEFT));
htsmsg_add_str(list, NULL, dvb_pol2str(POLARISATION_CIRCULAR_RIGHT));
return list;
}
static const void *
linuxdvb_mux_dvbs_class_modulation_get ( void *o )
{
static const char *s;
#if DVB_VER_ATLEAST(5,0)
linuxdvb_mux_t *lm = o;
s = dvb_qam2str(lm->lm_tuning.dmc_fe_modulation);
#endif
return &s;
}
static int
linuxdvb_mux_dvbs_class_modulation_set (void *o, const void *s)
{
#if DVB_VER_ATLEAST(5,0)
int mod = dvb_str2qam(s);
linuxdvb_mux_t *lm = o;
if (mod != lm->lm_tuning.dmc_fe_modulation) {
lm->lm_tuning.dmc_fe_modulation = mod;
return 1;
}
#endif
return 0;
}
static htsmsg_t *
linuxdvb_mux_dvbs_class_modulation_list ( void *o )
{
htsmsg_t *list = htsmsg_create_list();
htsmsg_add_str(list, NULL, dvb_qam2str(QAM_AUTO));
htsmsg_add_str(list, NULL, dvb_qam2str(QPSK));
htsmsg_add_str(list, NULL, dvb_qam2str(QAM_16));
#if DVB_VER_ATLEAST(5,4)
htsmsg_add_str(list, NULL, dvb_qam2str(PSK_8));
htsmsg_add_str(list, NULL, dvb_qam2str(APSK_16));
htsmsg_add_str(list, NULL, dvb_qam2str(APSK_32));
#endif
return list;
}
#if DVB_VER_ATLEAST(5,0)
static const void *
linuxdvb_mux_dvbs_class_rolloff_get ( void *o )
{
static const char *s;
linuxdvb_mux_t *lm = o;
s = dvb_rolloff2str(lm->lm_tuning.dmc_fe_rolloff);
return &s;
}
static int
linuxdvb_mux_dvbs_class_rolloff_set ( void *o, const void *s )
{
linuxdvb_mux_t *lm = o;
lm->lm_tuning.dmc_fe_rolloff = dvb_str2rolloff(s);
return 1;
}
static htsmsg_t *
linuxdvb_mux_dvbs_class_rolloff_list ( void *o )
{
htsmsg_t *list = htsmsg_create_list();
htsmsg_add_str(list, NULL, dvb_rolloff2str(ROLLOFF_35));
// Note: this is a bit naff, as the below values are only relevant
// to S2 muxes, but currently have no way to model that
htsmsg_add_str(list, NULL, dvb_rolloff2str(ROLLOFF_20));
htsmsg_add_str(list, NULL, dvb_rolloff2str(ROLLOFF_25));
htsmsg_add_str(list, NULL, dvb_rolloff2str(ROLLOFF_AUTO));
return list;
}
static const void *
linuxdvb_mux_dvbs_class_pilot_get ( void *o )
{
static const char *s;
linuxdvb_mux_t *lm = o;
s = dvb_pilot2str(lm->lm_tuning.dmc_fe_pilot);
return &s;
}
static int
linuxdvb_mux_dvbs_class_pilot_set ( void *o, const void *s )
{
linuxdvb_mux_t *lm = o;
lm->lm_tuning.dmc_fe_pilot = dvb_str2pilot(s);
return 1;
}
static htsmsg_t *
linuxdvb_mux_dvbs_class_pilot_list ( void *o )
{
htsmsg_t *list = htsmsg_create_list();
htsmsg_add_str(list, NULL, dvb_pilot2str(PILOT_AUTO));
htsmsg_add_str(list, NULL, dvb_pilot2str(PILOT_ON));
htsmsg_add_str(list, NULL, dvb_pilot2str(PILOT_OFF));
return list;
}
#endif
#define linuxdvb_mux_dvbs_class_delsys_get linuxdvb_mux_class_delsys_get
#define linuxdvb_mux_dvbs_class_delsys_set linuxdvb_mux_class_delsys_set
static htsmsg_t *
linuxdvb_mux_dvbs_class_delsys_enum (void *o)
{
htsmsg_t *list = htsmsg_create_list();
#if DVB_VER_ATLEAST(5,0)
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_DVBS));
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_DVBS2));
#endif
return list;
}
static const void *
linuxdvb_mux_dvbs_class_orbital_get ( void *o )
{
static char buf[256], *s = buf;
linuxdvb_mux_t *lm = o;
snprintf(buf, sizeof(buf), "%0.1f%c",
lm->lm_tuning.dmc_fe_orbital_pos / 10.0,
lm->lm_tuning.dmc_fe_orbital_dir);
return &s;
}
static int
linuxdvb_mux_dvbs_class_orbital_set ( void *o, const void *s )
{
int pos, save = 0;
char dir;
char *tmp = tvh_strdupa(s);
linuxdvb_mux_t *lm = o;
dir = tmp[strlen(tmp)-1];
tmp[strlen(tmp)-1] = '\0';
pos = (int)floorf(atof(tmp) * 10.0);
if (pos != lm->lm_tuning.dmc_fe_orbital_pos ||
dir != lm->lm_tuning.dmc_fe_orbital_dir) {
lm->lm_tuning.dmc_fe_orbital_pos = pos;
lm->lm_tuning.dmc_fe_orbital_dir = dir;
save = 1;
}
return save;
}
const idclass_t linuxdvb_mux_dvbs_class =
{
.ic_super = &linuxdvb_mux_class,
.ic_class = "linuxdvb_mux_dvbs",
.ic_caption = "Linux DVB-S Multiplex",
.ic_properties = (const property_t[]){
{
MUX_PROP_STR("delsys", "Delivery System", dvbs, delsys, "DVBS"),
},
{
.type = PT_U32,
.id = "frequency",
.name = "Frequency (kHz)",
.opts = PO_WRONCE,
.off = offsetof(linuxdvb_mux_t, lm_tuning.dmc_fe_params.frequency),
},
{
.type = PT_U32,
.id = "symbolrate",
.name = "Symbol Rate (Sym/s)",
.opts = PO_WRONCE,
.off = offsetof(linuxdvb_mux_t, lm_tuning.dmc_fe_params.u.qpsk.symbol_rate),
},
{
MUX_PROP_STR("polarisation", "Polarisation", dvbs, polarity, NULL)
},
{
.type = PT_STR,
.id = "modulation",
.name = "Modulation",
.set = linuxdvb_mux_dvbs_class_modulation_set,
.get = linuxdvb_mux_dvbs_class_modulation_get,
.list = linuxdvb_mux_dvbs_class_modulation_list,
.def.s = "AUTO",
},
{
MUX_PROP_STR("fec", "FEC", dvbs, fec, "AUTO")
},
#if DVB_VER_ATLEAST(5,0)
{
.type = PT_STR,
.id = "rolloff",
.name = "Rolloff",
.set = linuxdvb_mux_dvbs_class_rolloff_set,
.get = linuxdvb_mux_dvbs_class_rolloff_get,
.list = linuxdvb_mux_dvbs_class_rolloff_list,
.def.s = "AUTO"
},
{
.type = PT_STR,
.id = "pilot",
.name = "Pilot",
.opts = PO_ADVANCED,
.set = linuxdvb_mux_dvbs_class_pilot_set,
.get = linuxdvb_mux_dvbs_class_pilot_get,
.list = linuxdvb_mux_dvbs_class_pilot_list,
},
#endif
{
.type = PT_STR,
.id = "orbital",
.name = "Orbital Pos.",
.set = linuxdvb_mux_dvbs_class_orbital_set,
.get = linuxdvb_mux_dvbs_class_orbital_get,
.opts = PO_ADVANCED | PO_RDONLY
},
{}
}
};
#define linuxdvb_mux_atsc_class_delsys_get linuxdvb_mux_class_delsys_get
#define linuxdvb_mux_atsc_class_delsys_set linuxdvb_mux_class_delsys_set
static htsmsg_t *
linuxdvb_mux_atsc_class_delsys_enum (void *o)
{
htsmsg_t *list = htsmsg_create_list();
#if DVB_VER_ATLEAST(5,0)
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_ATSC));
htsmsg_add_str(list, NULL, dvb_delsys2str(SYS_ATSCMH));
#endif
return list;
}
linuxdvb_mux_class_X(atsc, vsb, modulation, qam,
QAM_AUTO, QAM_256, VSB_8);
const idclass_t linuxdvb_mux_atsc_class =
{
.ic_super = &linuxdvb_mux_class,
.ic_class = "linuxdvb_mux_atsc",
.ic_caption = "Linux ATSC Multiplex",
.ic_properties = (const property_t[]){
{
MUX_PROP_STR("delsys", "Delivery System", atsc, delsys, "ATSC"),
},
{
.type = PT_U32,
.id = "frequency",
.name = "Frequency (kHz)",
.opts = PO_WRONCE,
.off = offsetof(linuxdvb_mux_t, lm_tuning.dmc_fe_params.frequency),
},
{
MUX_PROP_STR("modulation", "Modulation", atsc, qam, "AUTO")
},
{}
}
};
/* **************************************************************************
* Class methods
* *************************************************************************/
static void
linuxdvb_mux_config_save ( mpegts_mux_t *mm )
{
htsmsg_t *c = htsmsg_create_map();
mpegts_mux_save(mm, c);
hts_settings_save(c, "input/linuxdvb/networks/%s/muxes/%s/config",
idnode_uuid_as_str(&mm->mm_network->mn_id),
idnode_uuid_as_str(&mm->mm_id));
htsmsg_destroy(c);
}
static void
linuxdvb_mux_display_name ( mpegts_mux_t *mm, char *buf, size_t len )
{
linuxdvb_mux_t *lm = (linuxdvb_mux_t*)mm;
linuxdvb_network_t *ln = (linuxdvb_network_t*)mm->mm_network;
uint32_t freq = lm->lm_tuning.dmc_fe_params.frequency;
char pol[2] = { 0 };
if (ln->ln_type == FE_QPSK) {
const char *s = dvb_pol2str(lm->lm_tuning.dmc_fe_polarisation);
if (s) pol[0] = *s;
freq /= 1000;
} else {
freq /= 1000;
}
snprintf(buf, len, "%d%s", freq, pol);
}
static void
linuxdvb_mux_create_instances ( mpegts_mux_t *mm )
{
mpegts_input_t *mi;
LIST_FOREACH(mi, &mm->mm_network->mn_inputs, mi_network_link) {
if (mi->mi_is_enabled(mi))
mi->mi_create_mux_instance(mi, mm);
}
}
static void
linuxdvb_mux_delete ( mpegts_mux_t *mm, int delconf )
{
/* Remove config */
if (delconf)
hts_settings_remove("input/linuxdvb/networks/%s/muxes/%s",
idnode_uuid_as_str(&mm->mm_network->mn_id),
idnode_uuid_as_str(&mm->mm_id));
/* Delete the mux */
mpegts_mux_delete(mm, delconf);
}
/* **************************************************************************
* Creation/Config
* *************************************************************************/
linuxdvb_mux_t *
linuxdvb_mux_create0
( linuxdvb_network_t *ln,
uint16_t onid, uint16_t tsid, const dvb_mux_conf_t *dmc,
const char *uuid, htsmsg_t *conf )
{
const idclass_t *idc;
mpegts_mux_t *mm;
linuxdvb_mux_t *lm;
htsmsg_t *c, *e;
htsmsg_field_t *f;
/* Class */
if (ln->ln_type == FE_QPSK)
idc = &linuxdvb_mux_dvbs_class;
else if (ln->ln_type == FE_QAM)
idc = &linuxdvb_mux_dvbc_class;
else if (ln->ln_type == FE_OFDM)
idc = &linuxdvb_mux_dvbt_class;
else if (ln->ln_type == FE_ATSC)
idc = &linuxdvb_mux_atsc_class;
else {
tvherror("linuxdvb", "unknown FE type %d", ln->ln_type);
return NULL;
}
/* Create */
if (!(mm = mpegts_mux_create0(calloc(1, sizeof(linuxdvb_mux_t)), idc, uuid,
(mpegts_network_t*)ln, onid, tsid, conf)))
return NULL;
lm = (linuxdvb_mux_t*)mm;
/* Tuning */
if (dmc)
memcpy(&lm->lm_tuning, dmc, sizeof(dvb_mux_conf_t));
/* Callbacks */
lm->mm_delete = linuxdvb_mux_delete;
lm->mm_display_name = linuxdvb_mux_display_name;
lm->mm_config_save = linuxdvb_mux_config_save;
lm->mm_create_instances = linuxdvb_mux_create_instances;
/* Defaults */
lm->lm_tuning.dmc_fe_params.inversion = INVERSION_AUTO;
#if DVB_VER_ATLEAST(5,0)
lm->lm_tuning.dmc_fe_pilot = PILOT_AUTO;
#endif
/* No config */
if (!conf) return lm;
/* Services */
c = hts_settings_load_r(1, "input/linuxdvb/networks/%s/muxes/%s/services",
idnode_uuid_as_str(&ln->mn_id),
idnode_uuid_as_str(&mm->mm_id));
if (c) {
HTSMSG_FOREACH(f, c) {
if (!(e = htsmsg_get_map_by_field(f))) continue;
(void)linuxdvb_service_create0(lm, 0, 0, f->hmf_name, e);
}
htsmsg_destroy(c);
}
return lm;
}