457 lines
11 KiB
C
457 lines
11 KiB
C
/*
|
|
* Tvheadend - MPEGTS input source
|
|
* 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 "input.h"
|
|
#include "subscriptions.h"
|
|
#include "channels.h"
|
|
#include "access.h"
|
|
#include "dvb_charset.h"
|
|
|
|
#include <assert.h>
|
|
|
|
/* ****************************************************************************
|
|
* Class definition
|
|
* ***************************************************************************/
|
|
|
|
static void
|
|
mpegts_network_class_save
|
|
( idnode_t *in )
|
|
{
|
|
mpegts_network_t *mn = (mpegts_network_t*)in;
|
|
if (mn->mn_config_save)
|
|
mn->mn_config_save(mn);
|
|
}
|
|
|
|
static const char *
|
|
mpegts_network_class_get_title ( idnode_t *in )
|
|
{
|
|
static char buf[256];
|
|
mpegts_network_t *mn = (mpegts_network_t*)in;
|
|
*buf = 0;
|
|
if (mn->mn_display_name)
|
|
mn->mn_display_name(mn, buf, sizeof(buf));
|
|
return buf;
|
|
}
|
|
|
|
static const void *
|
|
mpegts_network_class_get_num_mux ( void *ptr )
|
|
{
|
|
static int n;
|
|
mpegts_mux_t *mm;
|
|
mpegts_network_t *mn = ptr;
|
|
|
|
n = 0;
|
|
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
|
|
n++;
|
|
|
|
return &n;
|
|
}
|
|
|
|
static const void *
|
|
mpegts_network_class_get_num_svc ( void *ptr )
|
|
{
|
|
static int n;
|
|
mpegts_mux_t *mm;
|
|
mpegts_service_t *s;
|
|
mpegts_network_t *mn = ptr;
|
|
|
|
n = 0;
|
|
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
|
|
LIST_FOREACH(s, &mm->mm_services, s_dvb_mux_link)
|
|
n++;
|
|
|
|
return &n;
|
|
}
|
|
|
|
static const void *
|
|
mpegts_network_class_get_num_chn ( void *ptr )
|
|
{
|
|
static int n;
|
|
mpegts_mux_t *mm;
|
|
mpegts_service_t *s;
|
|
mpegts_network_t *mn = ptr;
|
|
channel_service_mapping_t *csm;
|
|
|
|
n = 0;
|
|
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
|
|
LIST_FOREACH(s, &mm->mm_services, s_dvb_mux_link)
|
|
LIST_FOREACH(csm, &s->s_channels, csm_svc_link)
|
|
n++;
|
|
|
|
return &n;
|
|
}
|
|
|
|
static const void *
|
|
mpegts_network_class_get_scanq_length ( void *ptr )
|
|
{
|
|
static __thread int n;
|
|
mpegts_mux_t *mm;
|
|
mpegts_network_t *mn = ptr;
|
|
|
|
n = 0;
|
|
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link)
|
|
if (mm->mm_scan_state != MM_SCAN_STATE_IDLE)
|
|
n++;
|
|
|
|
return &n;
|
|
}
|
|
|
|
static void
|
|
mpegts_network_class_idlescan_notify ( void *p )
|
|
{
|
|
mpegts_network_t *mn = p;
|
|
mpegts_mux_t *mm;
|
|
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link) {
|
|
if (mn->mn_idlescan)
|
|
mpegts_network_scan_queue_add(mm, SUBSCRIPTION_PRIO_SCAN_IDLE);
|
|
else if (mm->mm_scan_state == MM_SCAN_STATE_PEND &&
|
|
mm->mm_scan_weight == SUBSCRIPTION_PRIO_SCAN_IDLE)
|
|
mpegts_network_scan_queue_del(mm);
|
|
}
|
|
}
|
|
|
|
const idclass_t mpegts_network_class =
|
|
{
|
|
.ic_class = "mpegts_network",
|
|
.ic_caption = "MPEGTS Network",
|
|
.ic_event = "mpegts_network",
|
|
.ic_perm_def = ACCESS_ADMIN,
|
|
.ic_save = mpegts_network_class_save,
|
|
.ic_get_title = mpegts_network_class_get_title,
|
|
.ic_properties = (const property_t[]){
|
|
{
|
|
.type = PT_STR,
|
|
.id = "networkname",
|
|
.name = "Network Name",
|
|
.off = offsetof(mpegts_network_t, mn_network_name),
|
|
.notify = idnode_notify_title_changed,
|
|
},
|
|
{
|
|
.type = PT_U16,
|
|
.id = "nid",
|
|
.name = "Network ID (limit scanning)",
|
|
.opts = PO_ADVANCED,
|
|
.off = offsetof(mpegts_network_t, mn_nid),
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "autodiscovery",
|
|
.name = "Network Discovery",
|
|
.off = offsetof(mpegts_network_t, mn_autodiscovery),
|
|
.def.i = 1
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "skipinitscan",
|
|
.name = "Skip Initial Scan",
|
|
.off = offsetof(mpegts_network_t, mn_skipinitscan),
|
|
.def.i = 1
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "idlescan",
|
|
.name = "Idle Scan Muxes",
|
|
.off = offsetof(mpegts_network_t, mn_idlescan),
|
|
.def.i = 0,
|
|
.notify = mpegts_network_class_idlescan_notify,
|
|
},
|
|
{
|
|
.type = PT_BOOL,
|
|
.id = "ignore_chnum",
|
|
.name = "Ignore Provider's Channel Numbers",
|
|
.off = offsetof(mpegts_network_t, mn_ignore_chnum),
|
|
.def.i = 0,
|
|
},
|
|
{
|
|
.type = PT_STR,
|
|
.id = "charset",
|
|
.name = "Character Set",
|
|
.off = offsetof(mpegts_network_t, mn_charset),
|
|
.list = dvb_charset_enum,
|
|
.opts = PO_ADVANCED,
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "num_mux",
|
|
.name = "# Muxes",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.get = mpegts_network_class_get_num_mux,
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "num_svc",
|
|
.name = "# Services",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.get = mpegts_network_class_get_num_svc,
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "num_chn",
|
|
.name = "# Channels",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.get = mpegts_network_class_get_num_chn,
|
|
},
|
|
{
|
|
.type = PT_INT,
|
|
.id = "scanq_length",
|
|
.name = "Scan Q length",
|
|
.opts = PO_RDONLY | PO_NOSAVE,
|
|
.get = mpegts_network_class_get_scanq_length,
|
|
},
|
|
{}
|
|
}
|
|
};
|
|
|
|
/* ****************************************************************************
|
|
* Class methods
|
|
* ***************************************************************************/
|
|
|
|
static void
|
|
mpegts_network_display_name
|
|
( mpegts_network_t *mn, char *buf, size_t len )
|
|
{
|
|
strncpy(buf, mn->mn_network_name ?: "unknown", len);
|
|
}
|
|
|
|
static void
|
|
mpegts_network_config_save
|
|
( mpegts_network_t *mn )
|
|
{
|
|
// Nothing - leave to child classes
|
|
}
|
|
|
|
static mpegts_mux_t *
|
|
mpegts_network_create_mux
|
|
( mpegts_mux_t *mm, uint16_t sid, uint16_t tsid, void *aux )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static mpegts_service_t *
|
|
mpegts_network_create_service
|
|
( mpegts_mux_t *mm, uint16_t sid, uint16_t pmt_pid )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static const idclass_t *
|
|
mpegts_network_mux_class
|
|
( mpegts_network_t *mn )
|
|
{
|
|
extern const idclass_t mpegts_mux_class;
|
|
return &mpegts_mux_class;
|
|
}
|
|
|
|
static mpegts_mux_t *
|
|
mpegts_network_mux_create2
|
|
( mpegts_network_t *mn, htsmsg_t *conf )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
mpegts_network_link_delete ( mpegts_network_link_t *mnl )
|
|
{
|
|
idnode_notify_simple(&mnl->mnl_input->ti_id);
|
|
LIST_REMOVE(mnl, mnl_mn_link);
|
|
LIST_REMOVE(mnl, mnl_mi_link);
|
|
free(mnl);
|
|
}
|
|
|
|
void
|
|
mpegts_network_delete
|
|
( mpegts_network_t *mn, int delconf )
|
|
{
|
|
mpegts_mux_t *mm;
|
|
mpegts_network_link_t *mnl;
|
|
|
|
/* Remove from global list */
|
|
LIST_REMOVE(mn, mn_global_link);
|
|
|
|
/* Delete all muxes */
|
|
while ((mm = LIST_FIRST(&mn->mn_muxes))) {
|
|
mm->mm_delete(mm, delconf);
|
|
}
|
|
|
|
/* Disarm scanning */
|
|
gtimer_disarm(&mn->mn_scan_timer);
|
|
|
|
/* Remove from input */
|
|
while ((mnl = LIST_FIRST(&mn->mn_inputs)))
|
|
mpegts_network_link_delete(mnl);
|
|
|
|
/* Free memory */
|
|
idnode_unlink(&mn->mn_id);
|
|
free(mn->mn_network_name);
|
|
free(mn->mn_charset);
|
|
free(mn);
|
|
}
|
|
|
|
/* ****************************************************************************
|
|
* Creation/Config
|
|
* ***************************************************************************/
|
|
|
|
mpegts_network_list_t mpegts_network_all;
|
|
|
|
mpegts_network_t *
|
|
mpegts_network_create0
|
|
( mpegts_network_t *mn, const idclass_t *idc, const char *uuid,
|
|
const char *netname, htsmsg_t *conf )
|
|
{
|
|
char buf[256];
|
|
|
|
/* Setup idnode */
|
|
if (idnode_insert(&mn->mn_id, uuid, idc, 0)) {
|
|
if (uuid)
|
|
tvherror("mpegts", "invalid network uuid '%s'", uuid);
|
|
free(mn);
|
|
return NULL;
|
|
}
|
|
|
|
/* Default callbacks */
|
|
mn->mn_display_name = mpegts_network_display_name;
|
|
mn->mn_config_save = mpegts_network_config_save;
|
|
mn->mn_create_mux = mpegts_network_create_mux;
|
|
mn->mn_create_service = mpegts_network_create_service;
|
|
mn->mn_mux_class = mpegts_network_mux_class;
|
|
mn->mn_mux_create2 = mpegts_network_mux_create2;
|
|
|
|
/* Add to global list */
|
|
LIST_INSERT_HEAD(&mpegts_network_all, mn, mn_global_link);
|
|
|
|
/* Initialise scanning */
|
|
TAILQ_INIT(&mn->mn_scan_pend);
|
|
TAILQ_INIT(&mn->mn_scan_active);
|
|
gtimer_arm(&mn->mn_scan_timer, mpegts_network_scan_timer_cb, mn, 0);
|
|
|
|
/* Load config */
|
|
if (conf)
|
|
idnode_load(&mn->mn_id, conf);
|
|
|
|
/* Name */
|
|
if (netname) mn->mn_network_name = strdup(netname);
|
|
mn->mn_display_name(mn, buf, sizeof(buf));
|
|
tvhtrace("mpegts", "created network %s", buf);
|
|
|
|
return mn;
|
|
}
|
|
|
|
void
|
|
mpegts_network_class_delete(const idclass_t *idc, int delconf)
|
|
{
|
|
mpegts_network_t *mn, *n;
|
|
|
|
for (mn = LIST_FIRST(&mpegts_network_all); mn != NULL; mn = n) {
|
|
n = LIST_NEXT(mn, mn_global_link);
|
|
if (mn->mn_id.in_class == idc)
|
|
mpegts_network_delete(mn, delconf);
|
|
}
|
|
}
|
|
|
|
int
|
|
mpegts_network_set_nid
|
|
( mpegts_network_t *mn, uint16_t nid )
|
|
{
|
|
char buf[256];
|
|
if (mn->mn_nid == nid)
|
|
return 0;
|
|
mn->mn_nid = nid;
|
|
mn->mn_display_name(mn, buf, sizeof(buf));
|
|
tvhdebug("mpegts", "%s - set nid %04X (%d)", buf, nid, nid);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
mpegts_network_set_network_name
|
|
( mpegts_network_t *mn, const char *name )
|
|
{
|
|
char buf[256];
|
|
if (mn->mn_network_name) return 0;
|
|
if (!name || name[0] == '\0' || !strcmp(name, mn->mn_network_name ?: ""))
|
|
return 0;
|
|
tvh_str_update(&mn->mn_network_name, name);
|
|
mn->mn_display_name(mn, buf, sizeof(buf));
|
|
tvhdebug("mpegts", "%s - set name %s", buf, name);
|
|
return 1;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Network classes/creation
|
|
*****************************************************************************/
|
|
|
|
mpegts_network_builder_list_t mpegts_network_builders;
|
|
|
|
void
|
|
mpegts_network_register_builder
|
|
( const idclass_t *idc,
|
|
mpegts_network_t *(*build) (const idclass_t *idc, htsmsg_t *conf) )
|
|
{
|
|
mpegts_network_builder_t *mnb = calloc(1, sizeof(mpegts_network_builder_t));
|
|
mnb->idc = idc;
|
|
mnb->build = build;
|
|
LIST_INSERT_HEAD(&mpegts_network_builders, mnb, link);
|
|
}
|
|
|
|
void
|
|
mpegts_network_unregister_builder
|
|
( const idclass_t *idc )
|
|
{
|
|
mpegts_network_builder_t *mnb;
|
|
LIST_FOREACH(mnb, &mpegts_network_builders, link) {
|
|
if (mnb->idc == idc) {
|
|
LIST_REMOVE(mnb, link);
|
|
free(mnb);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
mpegts_network_t *
|
|
mpegts_network_build
|
|
( const char *clazz, htsmsg_t *conf )
|
|
{
|
|
mpegts_network_builder_t *mnb;
|
|
LIST_FOREACH(mnb, &mpegts_network_builders, link) {
|
|
if (!strcmp(mnb->idc->ic_class, clazz))
|
|
return mnb->build(mnb->idc, conf);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Search
|
|
*****************************************************************************/
|
|
|
|
mpegts_mux_t *
|
|
mpegts_network_find_mux
|
|
( mpegts_network_t *mn, uint16_t onid, uint16_t tsid )
|
|
{
|
|
mpegts_mux_t *mm;
|
|
LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link) {
|
|
if (mm->mm_onid && onid && mm->mm_onid != onid) continue;
|
|
if (mm->mm_tsid == tsid)
|
|
break;
|
|
}
|
|
return mm;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Editor Configuration
|
|
*
|
|
* vim:sts=2:ts=2:sw=2:et
|
|
*****************************************************************************/
|