tvheadend/src/input/mpegts/mpegts_network_scan.c
2015-03-09 16:15:37 +01:00

424 lines
13 KiB
C

/*
* Tvheadend - Network Scanner
*
* Copyright (C) 2014 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 "input.h"
/******************************************************************************
* Timer
*****************************************************************************/
/* Notify */
static void
mpegts_network_scan_notify ( mpegts_mux_t *mm )
{
idnode_notify_simple(&mm->mm_id);
idnode_notify_simple(&mm->mm_network->mn_id);
}
static int
mm_cmp ( mpegts_mux_t *a, mpegts_mux_t *b )
{
return b->mm_scan_weight - a->mm_scan_weight;
}
void
mpegts_network_scan_timer_cb ( void *p )
{
mpegts_network_t *mn = p;
mpegts_mux_t *mm, *nxt = NULL;
int r;
/* Process Q */
for (mm = TAILQ_FIRST(&mn->mn_scan_pend); mm != NULL; mm = nxt) {
nxt = TAILQ_NEXT(mm, mm_scan_link);
assert(mm->mm_scan_state == MM_SCAN_STATE_PEND);
/* Don't try to subscribe already tuned muxes */
if (mm->mm_active) continue;
/* Attempt to tune */
r = mpegts_mux_subscribe(mm, NULL, "scan", mm->mm_scan_weight, mm->mm_scan_flags);
/* Started */
if (!r) {
assert(mm->mm_scan_state == MM_SCAN_STATE_ACTIVE);
continue;
}
assert(mm->mm_scan_state == MM_SCAN_STATE_PEND);
/* No free tuners - stop */
if (r == SM_CODE_NO_FREE_ADAPTER)
break;
/* No valid tuners (subtly different, might be able to tuner a later
* mux)
*/
if (r == SM_CODE_NO_VALID_ADAPTER && mm->mm_is_enabled(mm))
continue;
/* Failed */
TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
if (mm->mm_scan_result != MM_SCAN_FAIL) {
mm->mm_scan_result = MM_SCAN_FAIL;
mm->mm_config_save(mm);
}
mm->mm_scan_state = MM_SCAN_STATE_IDLE;
mm->mm_scan_weight = 0;
mpegts_network_scan_notify(mm);
}
/* Re-arm timer. Really this is just a safety measure as we'd normally
* expect the timer to be forcefully triggered on finish of a mux scan
*/
gtimer_arm(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, 120);
}
/******************************************************************************
* Mux transition
*****************************************************************************/
/* Finished */
static inline void
mpegts_network_scan_mux_done0
( mpegts_mux_t *mm, mpegts_mux_scan_result_t result, int weight )
{
mpegts_network_t *mn = mm->mm_network;
mpegts_mux_unsubscribe_by_name(mm, "scan");
if (mm->mm_scan_state == MM_SCAN_STATE_PEND) {
if (weight || mn->mn_idlescan) {
if (!weight)
mm->mm_scan_weight = SUBSCRIPTION_PRIO_SCAN_IDLE;
TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
TAILQ_INSERT_SORTED_R(&mn->mn_scan_pend, mpegts_mux_queue,
mm, mm_scan_link, mm_cmp);
gtimer_arm(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, 10);
weight = 0;
} else {
mpegts_network_scan_queue_del(mm);
}
} else {
if (!weight && mn->mn_idlescan)
weight = SUBSCRIPTION_PRIO_SCAN_IDLE;
mpegts_network_scan_queue_del(mm);
}
if (result != MM_SCAN_NONE && mm->mm_scan_result != result) {
mm->mm_scan_result = result;
mm->mm_config_save(mm);
}
/* Re-enable? */
if (weight > 0)
mpegts_network_scan_queue_add(mm, weight, mm->mm_scan_flags, 10);
}
/* Failed - couldn't start */
void
mpegts_network_scan_mux_fail ( mpegts_mux_t *mm )
{
mpegts_network_scan_mux_done0(mm, MM_SCAN_FAIL, 0);
}
/* Completed succesfully */
void
mpegts_network_scan_mux_done ( mpegts_mux_t *mm )
{
mm->mm_scan_flags = 0;
mpegts_network_scan_mux_done0(mm, MM_SCAN_OK, 0);
}
/* Failed - no input */
void
mpegts_network_scan_mux_timeout ( mpegts_mux_t *mm )
{
mpegts_network_scan_mux_done0(mm, MM_SCAN_FAIL, 0);
}
/* Interrupted (re-add) */
void
mpegts_network_scan_mux_cancel ( mpegts_mux_t *mm, int reinsert )
{
if (mm->mm_scan_state != MM_SCAN_STATE_ACTIVE)
return;
if (!reinsert)
mm->mm_scan_flags = 0;
mpegts_network_scan_mux_done0(mm, MM_SCAN_NONE,
reinsert ? mm->mm_scan_weight : 0);
}
/* Mux has been started */
void
mpegts_network_scan_mux_active ( mpegts_mux_t *mm )
{
mpegts_network_t *mn = mm->mm_network;
if (mm->mm_scan_state != MM_SCAN_STATE_PEND)
return;
mm->mm_scan_state = MM_SCAN_STATE_ACTIVE;
mm->mm_scan_init = 0;
TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
TAILQ_INSERT_TAIL(&mn->mn_scan_active, mm, mm_scan_link);
}
/******************************************************************************
* Mux queue handling
*****************************************************************************/
void
mpegts_network_scan_queue_del ( mpegts_mux_t *mm )
{
mpegts_network_t *mn = mm->mm_network;
char buf[256], buf2[256];
if (mm->mm_scan_state == MM_SCAN_STATE_ACTIVE) {
TAILQ_REMOVE(&mn->mn_scan_active, mm, mm_scan_link);
} else if (mm->mm_scan_state == MM_SCAN_STATE_PEND) {
TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
}
mpegts_mux_nice_name(mm, buf, sizeof(buf));
mn->mn_display_name(mn, buf2, sizeof(buf2));
tvhdebug("mpegts", "%s - removing mux %s from scan queue", buf2, buf);
mm->mm_scan_state = MM_SCAN_STATE_IDLE;
mm->mm_scan_weight = 0;
gtimer_disarm(&mm->mm_scan_timeout);
gtimer_arm(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, 0);
mpegts_network_scan_notify(mm);
}
void
mpegts_network_scan_queue_add
( mpegts_mux_t *mm, int weight, int flags, int delay )
{
int reload = 0;
char buf[256], buf2[256];;
mpegts_network_t *mn = mm->mm_network;
if (!mm->mm_is_enabled(mm)) return;
if (weight <= 0) return;
if (weight > mm->mm_scan_weight) {
mm->mm_scan_weight = weight;
reload = 1;
}
/* Already active */
if (mm->mm_scan_state == MM_SCAN_STATE_ACTIVE)
return;
/* Remove entry (or ignore) */
if (mm->mm_scan_state == MM_SCAN_STATE_PEND) {
if (!reload)
return;
TAILQ_REMOVE(&mn->mn_scan_pend, mm, mm_scan_link);
}
mpegts_mux_nice_name(mm, buf, sizeof(buf));
mn->mn_display_name(mn, buf2, sizeof(buf2));
tvhdebug("mpegts", "%s - adding mux %s to scan queue weight %d flags %04X",
buf2, buf, weight, flags);
/* Add new entry */
mm->mm_scan_state = MM_SCAN_STATE_PEND;
mm->mm_scan_flags |= flags;
if (mm->mm_scan_flags == 0)
mm->mm_scan_flags = SUBSCRIPTION_IDLESCAN;
TAILQ_INSERT_SORTED_R(&mn->mn_scan_pend, mpegts_mux_queue,
mm, mm_scan_link, mm_cmp);
gtimer_arm(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, delay);
mpegts_network_scan_notify(mm);
}
/******************************************************************************
* Bouquet helper
*****************************************************************************/
#if ENABLE_MPEGTS_DVB
static ssize_t
startswith( const char *str, const char *start )
{
size_t len = strlen(start);
if (!strncmp(str, start, len))
return len;
return -1;
}
#endif
void
mpegts_mux_bouquet_rescan ( const char *src, const char *extra )
{
#if ENABLE_MPEGTS_DVB
mpegts_network_t *mn;
mpegts_mux_t *mm;
ssize_t l;
const idclass_t *ic;
uint32_t freq;
int satpos;
#endif
if (!src)
return;
#if ENABLE_MPEGTS_DVB
if ((l = startswith(src, "dvb-bouquet://dvbs,")) > 0) {
uint32_t tsid, nbid;
src += l;
if ((satpos = dvb_sat_position_from_str(src)) == INT_MAX)
return;
while (*src && *src != ',')
src++;
if (sscanf(src, ",%x,%x", &tsid, &nbid) != 2)
return;
LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) &&
mm->mm_tsid == tsid &&
((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.orbital_pos == satpos)
mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
return;
}
if ((l = startswith(src, "dvb-bouquet://dvbt,")) > 0) {
uint32_t tsid, nbid;
if (sscanf(src, "%x,%x", &tsid, &nbid) != 2)
return;
ic = &dvb_mux_dvbt_class;
goto tsid_lookup;
}
if ((l = startswith(src, "dvb-bouquet://dvbc,")) > 0) {
uint32_t tsid, nbid;
if (sscanf(src, "%x,%x", &tsid, &nbid) != 2)
return;
ic = &dvb_mux_dvbc_class;
tsid_lookup:
LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
if (idnode_is_instance(&mm->mm_id, ic) &&
mm->mm_tsid == tsid)
mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
return;
}
if ((l = startswith(src, "dvb-bskyb://dvbs,")) > 0 ||
(l = startswith(src, "dvb-freesat://dvbs,")) > 0) {
if ((satpos = dvb_sat_position_from_str(src + l)) == INT_MAX)
return;
/* a bit tricky, but we don't have other info */
if (!extra)
return;
freq = strtod(extra, NULL) * 1000;
LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) &&
abs(((dvb_mux_t *)mm)->lm_tuning.dmc_fe_freq - freq) < 2000 &&
((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.orbital_pos == satpos)
mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
return;
}
if ((l = startswith(src, "dvb-fastscan://")) > 0) {
uint32_t pid, symbol, dvbs2;
char pol[2];
pol[1] = '\0';
src += l;
if ((l = startswith(src, "DVBS2,")) > 0)
dvbs2 = 1;
else if ((l = startswith(src, "DVBS,")) > 0)
dvbs2 = 0;
else
return;
src += l;
if ((satpos = dvb_sat_position_from_str(src)) == INT_MAX)
return;
while (*src && *src != ',')
src++;
if (sscanf(src, ",%u,%c,%u,%u", &freq, &pol[0], &symbol, &pid) != 4)
return;
// search for fastscan mux
LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) &&
abs(((dvb_mux_t *)mm)->lm_tuning.dmc_fe_freq - freq) < 2000 &&
((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.polarisation == dvb_str2pol(pol) &&
((dvb_mux_t *)mm)->lm_tuning.u.dmc_fe_qpsk.orbital_pos == satpos)
{
char buf[256];
mpegts_mux_nice_name(mm, buf, sizeof(buf));
tvhinfo("mpegts", "fastscan mux found '%s', set scan state 'PENDING'", buf);
mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND);
return;
}
tvhinfo("mpegts", "fastscan mux not found, position:%i, frequency:%i, polarisation:%c", satpos, freq, pol[0]);
// fastscan mux not found, try to add it automatically
LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
if (mn->mn_satpos != INT_MAX && mn->mn_satpos == satpos)
{
dvb_mux_conf_t *mux;
mpegts_mux_t *mm = NULL;
mux = malloc(sizeof(dvb_mux_conf_t));
dvb_mux_conf_init(mux, dvbs2 ? DVB_SYS_DVBS2 : DVB_SYS_DVBS);
mux->dmc_fe_freq = freq;
mux->u.dmc_fe_qpsk.symbol_rate = symbol;
mux->u.dmc_fe_qpsk.polarisation = dvb_str2pol(pol);
mux->u.dmc_fe_qpsk.orbital_pos = satpos;
mux->u.dmc_fe_qpsk.fec_inner = DVB_FEC_AUTO;
mux->dmc_fe_modulation = DVB_MOD_AUTO;
mux->dmc_fe_rolloff = DVB_ROLLOFF_AUTO;
mux->dmc_fe_pls_code = 1;
mm = (mpegts_mux_t*)dvb_mux_create0((dvb_network_t*)mn,
MPEGTS_ONID_NONE,
MPEGTS_TSID_NONE,
mux, NULL, NULL);
if (mm)
{
mm->mm_config_save(mm);
char buf[256];
mn->mn_display_name(mn, buf, sizeof(buf));
tvhinfo("mpegts", "fastscan mux add to network '%s'", buf);
}
free(mux);
}
return;
}
#endif
}
/******************************************************************************
* Subsystem setup / tear down
*****************************************************************************/
void
mpegts_network_scan_init ( void )
{
}
void
mpegts_network_scan_done ( void )
{
}
/******************************************************************************
* Editor Configuration
*
* vim:sts=2:ts=2:sw=2:et
*****************************************************************************/