bouquet,fastscan: initial implementation

This commit is contained in:
Jaroslav Kysela 2014-10-29 17:01:11 +01:00
parent a9b1d171be
commit 7134315e0f
21 changed files with 899 additions and 24 deletions

View file

@ -152,7 +152,8 @@ SRCS = src/version.c \
src/cron.c \
src/esfilter.c \
src/intlconv.c \
src/profile.c
src/profile.c \
src/bouquet.c
SRCS-${CONFIG_UPNP} += \
src/upnp.c
@ -173,7 +174,8 @@ SRCS += \
src/api/api_access.c \
src/api/api_dvr.c \
src/api/api_caclient.c \
src/api/api_profile.c
src/api/api_profile.c \
src/api/api_bouquet.c
SRCS += \
src/parsers/parsers.c \
@ -229,6 +231,7 @@ SRCS-$(CONFIG_MPEGTS) += \
src/input/mpegts/dvb_support.c \
src/input/mpegts/dvb_charset.c \
src/input/mpegts/dvb_psi.c \
src/input/mpegts/fastscan.c \
src/input/mpegts/tsdemux.c \
src/input/mpegts/mpegts_mux_sched.c \
src/input/mpegts/mpegts_network_scan.c \

50
data/conf/fastscan Normal file
View file

@ -0,0 +1,50 @@
[
{
"name": "Canal Digitaal",
"position": 192,
"frequency": 12515000,
"pid": 900
},
{
"name": "TV Vlaanderen",
"position": 192,
"frequency": 12515000,
"pid": 901
},
{
"name": "TéléSAT",
"position": 192,
"frequency": 12515000,
"pid": 920
},
{
"name": "Mobistar NL",
"position": 192,
"frequency": 12515000,
"pid": 930
},
{
"name": "Mobistar FR",
"position": 192,
"frequency": 12515000,
"pid": 940
},
{
"name": "AustriaSat",
"position": 192,
"frequency": 12515000,
"pid": 950
},
{
"name": "Skylink: Czech Republic",
"position": 235,
"frequency": 12070000,
"pid": 30
},
{
"name": "Skylink: Slovak Republic",
"position": 235,
"frequency": 12070000,
"pid": 31
}
]

View file

@ -125,6 +125,7 @@ void api_init ( void )
api_mpegts_init();
api_service_init();
api_channel_init();
api_bouquet_init();
api_epg_init();
api_epggrab_init();
api_status_init();

View file

@ -64,6 +64,7 @@ void api_idnode_init ( void );
void api_input_init ( void );
void api_service_init ( void );
void api_channel_init ( void );
void api_bouquet_init ( void );
void api_mpegts_init ( void );
void api_epg_init ( void );
void api_epggrab_init ( void );

70
src/api/api_bouquet.c Normal file
View file

@ -0,0 +1,70 @@
/*
* API - bouquet calls
*
* Copyright (C) 2014 Jaroslav Kysela
*
* 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/>.
*/
#ifndef __TVH_API_BOUQUET_H__
#define __TVH_API_BOUQUET_H__
#include "tvheadend.h"
#include "bouquet.h"
#include "access.h"
#include "api.h"
static void
api_bouquet_grid
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf )
{
bouquet_t *bq;
RB_FOREACH(bq, &bouquets, bq_link)
idnode_set_add(ins, (idnode_t*)bq, &conf->filter);
}
static int
api_bouquet_create
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *conf;
bouquet_t *bq;
if (!(conf = htsmsg_get_map(args, "conf")))
return EINVAL;
pthread_mutex_lock(&global_lock);
bq = bouquet_create(NULL, conf, NULL, NULL);
if (bq)
bouquet_save(bq, 0);
pthread_mutex_unlock(&global_lock);
return 0;
}
void api_bouquet_init ( void )
{
static api_hook_t ah[] = {
{ "bouquet/class", ACCESS_ADMIN, api_idnode_class, (void*)&bouquet_class },
{ "bouquet/grid", ACCESS_ADMIN, api_idnode_grid, api_bouquet_grid },
{ "bouquet/create", ACCESS_ADMIN, api_bouquet_create, NULL },
{ NULL },
};
api_register_all(ah);
}
#endif /* __TVH_API_BOUQUET_H__ */

View file

@ -17,8 +17,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TVH_API_SERVICE_H__
#define __TVH_API_SERVICE_H__
#ifndef __TVH_API_CHANNEL_H__
#define __TVH_API_CHANNEL_H__
#include "tvheadend.h"
#include "channels.h"
@ -145,4 +145,4 @@ void api_channel_init ( void )
}
#endif /* __TVH_API_IDNODE_H__ */
#endif /* __TVH_API_CHANNEL_H__ */

View file

@ -199,4 +199,4 @@ void api_service_init ( void )
}
#endif /* __TVH_API_IDNODE_H__ */
#endif /* __TVH_API_SERVICE_H__ */

387
src/bouquet.c Normal file
View file

@ -0,0 +1,387 @@
/*
* tvheadend, bouquets
* Copyright (C) 2014 Jaroslav Kysela
*
* 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 "settings.h"
#include "access.h"
#include "bouquet.h"
#include "service.h"
bouquet_tree_t bouquets;
/**
*
*/
static int
_bq_cmp(const void *a, const void *b)
{
return strcmp(((bouquet_t *)a)->bq_src ?: "", ((bouquet_t *)b)->bq_src ?: "");
}
/**
*
*/
bouquet_t *
bouquet_create(const char *uuid, htsmsg_t *conf,
const char *name, const char *src)
{
bouquet_t *bq, *bq2;
int i;
lock_assert(&global_lock);
bq = calloc(1, sizeof(bouquet_t));
bq->bq_services = idnode_set_create();
if (idnode_insert(&bq->bq_id, uuid, &bouquet_class, 0)) {
if (uuid)
tvherror("bouquet", "invalid uuid '%s'", uuid);
free(bq);
return NULL;
}
if (conf) {
bq->bq_in_load = 1;
idnode_load(&bq->bq_id, conf);
bq->bq_in_load = 0;
if (!htsmsg_get_bool(conf, "shield", &i) && i)
bq->bq_shield = 1;
}
if (name) {
free(bq->bq_name);
bq->bq_name = strdup(name);
}
if (src) {
free(bq->bq_src);
bq->bq_src = strdup(src);
}
bq2 = RB_INSERT_SORTED(&bouquets, bq, bq_link, _bq_cmp);
assert(bq2 == NULL);
bq->bq_saveflag = 1;
return bq;
}
/**
*
*/
static void
bouquet_destroy(bouquet_t *bq)
{
if (!bq)
return;
RB_REMOVE(&bouquets, bq, bq_link);
idnode_unlink(&bq->bq_id);
idnode_set_free(bq->bq_services);
free(bq->bq_name);
free(bq->bq_src);
free(bq);
}
/**
*
*/
void
bouquet_destroy_by_service(service_t *t)
{
bouquet_t *bq;
lock_assert(&global_lock);
RB_FOREACH(bq, &bouquets, bq_link)
if (idnode_set_exists(bq->bq_services, &t->s_id))
idnode_set_remove(bq->bq_services, &t->s_id);
}
/*
*
*/
bouquet_t *
bouquet_find_by_source(const char *name, const char *src, int create)
{
bouquet_t *bq;
bouquet_t bqs;
assert(src);
lock_assert(&global_lock);
bqs.bq_src = (char *)src;
bq = RB_FIND(&bouquets, &bqs, bq_link, _bq_cmp);
if (bq)
return bq;
if (create && name)
return bouquet_create(NULL, NULL, name, src);
return NULL;
}
/*
*
*/
void
bouquet_add_service(bouquet_t *bq, service_t *s)
{
lock_assert(&global_lock);
if (!idnode_set_exists(bq->bq_services, &s->s_id)) {
idnode_set_add(bq->bq_services, &s->s_id, NULL);
bq->bq_saveflag = 1;
}
}
/**
*
*/
void
bouquet_save(bouquet_t *bq, int notify)
{
htsmsg_t *c = htsmsg_create_map();
idnode_save(&bq->bq_id, c);
hts_settings_save(c, "bouquet/%s", idnode_uuid_as_str(&bq->bq_id));
if (bq->bq_shield)
htsmsg_add_bool(c, "shield", 1);
htsmsg_destroy(c);
bq->bq_saveflag = 0;
if (notify)
idnode_notify_simple(&bq->bq_id);
}
/* **************************************************************************
* Class definition
* **************************************************************************/
static void
bouquet_class_save(idnode_t *self)
{
bouquet_save((bouquet_t *)self, 0);
}
static void
bouquet_class_delete(idnode_t *self)
{
bouquet_t *bq = (bouquet_t *)self;
if (!bq->bq_shield) {
hts_settings_remove("bouquet/%s", idnode_uuid_as_str(&bq->bq_id));
bouquet_destroy(bq);
} else {
idnode_set_free(bq->bq_services);
bq->bq_services = idnode_set_create();
bouquet_save(bq, 1);
}
}
static const char *
bouquet_class_get_title (idnode_t *self)
{
bouquet_t *bq = (bouquet_t *)self;
if (bq->bq_comment && bq->bq_comment[0] != '\0')
return bq->bq_comment;
return bq->bq_name ?: "";
}
static void
bouquet_class_enabled_notify ( void *obj )
{
bouquet_t *bq = obj;
service_t *s;
size_t z;
if (!bq->bq_enabled) {
for (z = 0; z < bq->bq_services->is_count; z++) {
s = (service_t *)bq->bq_services->is_array[z];
if (s->s_master_bouquet == bq)
s->s_master_bouquet = NULL;
}
}
}
static const void *
bouquet_class_services_get ( void *obj )
{
htsmsg_t *l = htsmsg_create_list();
bouquet_t *bq = obj;
size_t z;
/* Add all */
for (z = 0; z < bq->bq_services->is_count; z++)
htsmsg_add_str(l, NULL, idnode_uuid_as_str(bq->bq_services->is_array[z]));
return l;
}
static char *
bouquet_class_services_rend ( void *obj )
{
bouquet_t *bq = obj;
char buf[32];
snprintf(buf, sizeof(buf), "Services Count %zi", bq->bq_services->is_count);
return strdup(buf);
}
static int
bouquet_class_services_set ( void *obj, const void *p )
{
bouquet_t *bq = obj;
if (bq->bq_services_waiting)
htsmsg_destroy(bq->bq_services_waiting);
bq->bq_services_waiting = NULL;
if (bq->bq_in_load)
bq->bq_services_waiting = htsmsg_copy((htsmsg_t *)p);
return 0;
}
static const void *
bouquet_class_services_count_get ( void *obj )
{
static uint32_t u32;
bouquet_t *bq = obj;
u32 = bq->bq_services->is_count;
return &u32;
}
const idclass_t bouquet_class = {
.ic_class = "bouquet",
.ic_caption = "Bouquet",
.ic_event = "bouquet",
.ic_perm_def = ACCESS_ADMIN,
.ic_save = bouquet_class_save,
.ic_get_title = bouquet_class_get_title,
.ic_delete = bouquet_class_delete,
.ic_properties = (const property_t[]){
{
.type = PT_BOOL,
.id = "enabled",
.name = "Enabled",
.off = offsetof(bouquet_t, bq_enabled),
.notify = bouquet_class_enabled_notify,
},
{
.type = PT_STR,
.id = "name",
.name = "Name",
.off = offsetof(bouquet_t, bq_name),
},
{
.type = PT_STR,
.id = "source",
.name = "Source",
.off = offsetof(bouquet_t, bq_src),
.opts = PO_RDONLY,
},
{
.type = PT_STR,
.islist = 1,
.id = "services",
.name = "Services",
.get = bouquet_class_services_get,
.set = bouquet_class_services_set,
.rend = bouquet_class_services_rend,
.opts = PO_RDONLY | PO_HIDDEN,
},
{
.type = PT_U32,
.id = "services_count",
.name = "# Services",
.get = bouquet_class_services_count_get,
.opts = PO_RDONLY | PO_NOSAVE,
},
{
.type = PT_STR,
.id = "comment",
.name = "Comment",
.off = offsetof(bouquet_t, bq_comment),
},
{
.type = PT_U32,
.id = "lcn_off",
.name = "Channel Number Offset",
.off = offsetof(bouquet_t, bq_lcn_offset),
},
{}
}
};
/**
*
*/
void
bouquet_init(void)
{
htsmsg_t *c, *m;
htsmsg_field_t *f;
RB_INIT(&bouquets);
/* Load */
if ((c = hts_settings_load("bouquet")) != NULL) {
HTSMSG_FOREACH(f, c) {
if (!(m = htsmsg_field_get_map(f))) continue;
(void)bouquet_create(f->hmf_name, m, NULL, NULL);
}
htsmsg_destroy(c);
}
}
void
bouquet_service_resolve(void)
{
bouquet_t *bq;
htsmsg_field_t *f;
service_t *s;
const char *str;
int saveflag;
lock_assert(&global_lock);
RB_FOREACH(bq, &bouquets, bq_link) {
if (!bq->bq_services_waiting)
continue;
saveflag = bq->bq_saveflag;
HTSMSG_FOREACH(f, bq->bq_services_waiting) {
if ((str = htsmsg_field_get_str(f))) {
s = service_find_by_identifier(str);
if (s)
bouquet_add_service(bq, s);
}
}
htsmsg_destroy(bq->bq_services_waiting);
bq->bq_services_waiting = NULL;
bq->bq_saveflag = saveflag;
}
}
void
bouquet_done(void)
{
bouquet_t *bq;
pthread_mutex_lock(&global_lock);
while ((bq = RB_FIRST(&bouquets)) != NULL)
bouquet_destroy(bq);
pthread_mutex_unlock(&global_lock);
}

88
src/bouquet.h Normal file
View file

@ -0,0 +1,88 @@
/*
* TV headend - Bouquets
* Copyright (C) 2014 Jaroslav Kysela
*
* 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/>.
*/
#ifndef BOUQUET_H_
#define BOUQUET_H_
#include "idnode.h"
#include "htsmsg.h"
#include "service.h"
typedef struct bouquet {
idnode_t bq_id;
RB_ENTRY(bouquet) bq_link;
int bq_saveflag;
int bq_in_load;
int bq_shield;
int bq_enabled;
char *bq_name;
char *bq_src;
char *bq_comment;
idnode_set_t *bq_services;
htsmsg_t *bq_services_waiting;
uint32_t bq_lcn_offset;
} bouquet_t;
typedef RB_HEAD(,bouquet) bouquet_tree_t;
extern bouquet_tree_t bouquets;
extern const idclass_t bouquet_class;
/**
*
*/
bouquet_t *
bouquet_create(const char *uuid, htsmsg_t *conf,
const char *name, const char *src);
/**
*
*/
void
bouquet_destroy_by_service(service_t *t);
/**
*
*/
bouquet_t *
bouquet_find_by_source(const char *name, const char *src, int create);
/**
*
*/
void
bouquet_add_service(bouquet_t *bq, service_t *s);
/**
*
*/
void
bouquet_save(bouquet_t *bq, int notify);
/**
*
*/
void bouquet_init(void);
void bouquet_service_resolve(void);
void bouquet_done(void);
#endif /* BOUQUET_H_ */

View file

@ -164,8 +164,8 @@ idnode_insert(idnode_t *in, const char *uuid, const idclass_t *class, int flags)
} while (c != NULL && --retries > 0);
if(c != NULL) {
fprintf(stderr, "Id node collision%s\n",
(flags & IDNODE_SHORT_UUID) ? " (short)" : "");
fprintf(stderr, "Id node collision (%s) %s\n",
uuid, (flags & IDNODE_SHORT_UUID) ? " (short)" : "");
abort();
}
tvhtrace("idnode", "insert node %s", idnode_uuid_as_str(in));
@ -958,6 +958,21 @@ idnode_set_add
is->is_array[is->is_count++] = in;
}
void
idnode_set_remove
( idnode_set_t *is, idnode_t *in )
{
size_t z;
for (z = 0; z < is->is_count; z++)
if (is->is_array[z] == in) {
memmove(&is->is_array[z], &is->is_array[z+1],
(is->is_count - z - 1) * sizeof(idnode_t *));
is->is_count--;
break;
}
}
int
idnode_set_exists
( idnode_set_t *is, idnode_t * in )

View file

@ -200,11 +200,12 @@ int idnode_filter
#define idnode_set_create() calloc(1, sizeof(idnode_set_t))
void idnode_set_add
( idnode_set_t *is, idnode_t *in, idnode_filter_t *filt );
void idnode_set_remove ( idnode_set_t *is, idnode_t *in );
int idnode_set_exists ( idnode_set_t *is, idnode_t *in );
void idnode_set_sort ( idnode_set_t *is, idnode_sort_t *s );
void idnode_set_sort ( idnode_set_t *is, idnode_sort_t *s );
void idnode_set_sort_by_title ( idnode_set_t *is );
htsmsg_t *idnode_set_as_htsmsg ( idnode_set_t *is );
void idnode_set_free ( idnode_set_t *is );
void idnode_set_free ( idnode_set_t *is );
#endif /* __TVH_IDNODE_H__ */

View file

@ -17,6 +17,7 @@
*/
#include "input.h"
#include "mpegts/fastscan.h"
void
mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
@ -27,6 +28,9 @@ mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
idclass_register(&mpegts_mux_class);
idclass_register(&mpegts_service_class);
/* FastScan init */
dvb_fastscan_init();
/* Network scanner */
#if ENABLE_MPEGTS
mpegts_network_scan_init();
@ -97,6 +101,7 @@ mpegts_done ( void )
#if ENABLE_TSFILE
tvhftrace("main", tsfile_done);
#endif
dvb_fastscan_done();
}
/******************************************************************************

View file

@ -425,9 +425,9 @@ struct mpegts_service
* Fields defined by DVB standard EN 300 468
*/
uint16_t s_dvb_service_id;
uint16_t s_dvb_channel_num;
uint32_t s_dvb_channel_num;
uint16_t s_dvb_channel_minor;
uint16_t s_dvb_service_id;
char *s_dvb_svcname;
char *s_dvb_provider;
char *s_dvb_cridauth;

View file

@ -59,6 +59,10 @@ struct mpegts_mux;
#define DVB_BAT_BASE 0x48
#define DVB_BAT_MASK 0xF8
#define DVB_FASTSCAN_NIT_BASE 0xBC
#define DVB_FASTSCAN_SDT_BASE 0xBD
#define DVB_FASTSCAN_MASK 0xFF
#define DVB_VCT_T_BASE 0xC8
#define DVB_VCT_C_BASE 0xC9
#define DVB_VCT_MASK 0xFF
@ -210,6 +214,8 @@ int dvb_nit_callback
(struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
int dvb_bat_callback
(struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
int dvb_fs_sdt_callback
(struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
int dvb_sdt_callback
(struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
int dvb_tdt_callback

View file

@ -24,6 +24,8 @@
#include "lang_codes.h"
#include "service.h"
#include "dvb_charset.h"
#include "bouquet.h"
#include "fastscan.h"
#include <assert.h>
#include <stdio.h>
@ -321,7 +323,7 @@ dvb_desc_service
static int
dvb_desc_service_list
( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm )
( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm, bouquet_t *bq )
{
uint16_t stype, sid;
int i;
@ -333,6 +335,8 @@ dvb_desc_service_list
if (mm) {
int save = 0;
s = mpegts_service_find(mm, sid, 0, 1, &save);
if (bq)
bouquet_add_service(bq, (service_t *)s);
if (save)
s->s_config_save((service_t*)s);
}
@ -342,7 +346,7 @@ dvb_desc_service_list
static int
dvb_desc_local_channel
( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm )
( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm, bouquet_t *bq )
{
int save = 0;
uint16_t sid, lcn;
@ -354,6 +358,11 @@ dvb_desc_local_channel
if (lcn && mm) {
mpegts_service_t *s = mpegts_service_find(mm, sid, 0, 0, &save);
if (s) {
if (bq && bq->bq_lcn_offset &&
(!s->s_master_bouquet || s->s_master_bouquet == bq)) {
lcn += bq->bq_lcn_offset;
s->s_master_bouquet = bq;
}
if (s->s_dvb_channel_num != lcn) {
s->s_dvb_channel_num = lcn;
s->s_config_save((service_t*)s);
@ -721,18 +730,20 @@ dvb_nit_callback
mpegts_network_t *mn = mm->mm_network;
char name[256], dauth[256];
mpegts_table_state_t *st = NULL;
bouquet_t *bq = NULL;
const char *charset;
/* Net/Bat ID */
nbid = (ptr[0] << 8) | ptr[1];
/* Begin */
if (tableid != 0x40 && tableid != 0x41 && tableid != 0x4A) return -1;
if (tableid != 0x40 && tableid != 0x41 && tableid != 0x4A && tableid != 0xBC)
return -1;
r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
if (r != 1) return r;
/* NIT */
if (tableid != 0x4A) {
if (tableid != 0x4A && tableid != 0xBC /* fastscan */) {
/* Specific NID */
if (mn->mn_nid) {
@ -764,8 +775,13 @@ dvb_nit_callback
}
}
/* Fastscan */
if (tableid == 0xBC) {
tvhdebug(mt->mt_name, "fastscan %04X (%d) [%s]", nbid, nbid, name);
bq = mt->mt_opaque;
/* BAT */
if (tableid == 0x4A) {
} else if (tableid == 0x4A) {
tvhdebug(mt->mt_name, "bouquet %04X (%d) [%s]", nbid, nbid, name);
/* NIT */
@ -780,7 +796,6 @@ dvb_nit_callback
DVB_LOOP_FOREACH(ptr, len, 0, lptr, llen, 6) {
tsid = (lptr[0] << 8) | lptr[1];
onid = (lptr[2] << 8) | lptr[3];
tvhdebug(mt->mt_name, " onid %04X (%d) tsid %04X (%d)", onid, onid, tsid, tsid);
/* Find existing mux */
LIST_FOREACH(mux, &mn->mn_muxes, mm_network_link)
@ -788,6 +803,33 @@ dvb_nit_callback
break;
charset = dvb_charset_find(mn, mux, NULL);
#if ENABLE_MPEGTS_DVB
dauth[0] = 0;
if (mux && *name && tableid == 0x4A /* BAT */) {
if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbs_class)) {
dvb_mux_conf_t *mc = &((dvb_mux_t *)mux)->lm_tuning;
if (mc->u.dmc_fe_qpsk.orbital_dir) {
int pos = mc->u.dmc_fe_qpsk.orbital_pos;
if (mc->u.dmc_fe_qpsk.orbital_dir == 'W')
pos = -pos;
snprintf(dauth, sizeof(dauth), "dvb-bouquet://dvbs,%d/%s", pos, name);
}
} else if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbt_class)) {
snprintf(dauth, sizeof(dauth), "dvb-bouquet://dvbt/%s", name);
} else if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbc_class)) {
snprintf(dauth, sizeof(dauth), "dvb-bouquet://dvbc/%s", name);
}
}
if (dauth[0]) {
bouquet_t *bq2 = bouquet_find_by_source(name, dauth, 1);
if (bq2 != bq && bq && bq->bq_saveflag)
bouquet_save(bq, 1);
bq = bq2;
}
#endif
tvhdebug(mt->mt_name, " onid %04X (%d) tsid %04X (%d) mux %p bq %p", onid, onid, tsid, tsid, mux, bq);
DVB_DESC_FOREACH(lptr, llen, 4, dlptr, dllen, dtag, dlen, dptr) {
tvhtrace(mt->mt_name, " dtag %02X dlen %d", dtag, dlen);
@ -839,17 +881,20 @@ dvb_nit_callback
mpegts_mux_set_crid_authority(mux, dauth);
break;
case DVB_DESC_LOCAL_CHAN:
if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux))
if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
return -1;
break;
case DVB_DESC_SERVICE_LIST:
if (dvb_desc_service_list(mt->mt_name, dptr, dlen, mux))
if (dvb_desc_service_list(mt->mt_name, dptr, dlen, mux, bq))
return -1;
break;
}
}
}
if (bq && bq->bq_saveflag)
bouquet_save(bq, 1);
/* End */
return dvb_table_end(mt, st, sect);
}
@ -940,7 +985,7 @@ dvb_sdt_callback
int r;
s->s_dvb_servicetype = stype;
save = 1;
tvhtrace("sdt", " type changed");
tvhtrace("sdt", " type changed (old %i)", s->s_dvb_servicetype);
/* Set tvh service type */
if ((r = dvb_servicetype_lookup(stype)) != -1)
@ -1100,6 +1145,144 @@ dvb_bat_callback
return dvb_nit_callback(mt, ptr, len, tableid);
}
#if ENABLE_MPEGTS_DVB
/*
* DVB fastscan table processing
*/
int
dvb_fs_sdt_callback
(mpegts_table_t *mt, const uint8_t *ptr, int len, int tableid)
{
int r, sect, last, ver;
uint8_t dtag;
int llen, dlen;
const uint8_t *lptr, *dptr;
uint16_t nbid = 0, onid, tsid, service_id;
mpegts_mux_t *mm = mt->mt_mux, *mux;
mpegts_network_t *mn = mm->mm_network;
mpegts_table_state_t *st = NULL;
bouquet_t *bq = mt->mt_opaque;
/* Fastscan ID */
nbid = (ptr[0] << 8) | ptr[1];
/* Begin */
if (tableid != 0xBD)
return -1;
r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
if (r != 1) return r;
if (len < 5) return -1;
ptr += 5;
len -= 5;
while (len > 0) {
const char *charset;
char sprov[256] = "", sname[256] = "";
mpegts_service_t *s;
int stype = 0, save = 0;
onid = (ptr[0] << 8) | ptr[1];
tsid = (ptr[2] << 8) | ptr[3];
service_id = (ptr[4] << 8) | ptr[5];
/* (ptr[6] << 8) | ptr[7] - video pid */
/* (ptr[7] << 8) | ptr[8] - audio pid */
/* (ptr[9] << 8) | ptr[10] - video ecm pid */
/* (ptr[10] << 8) | ptr[12] - audio ecm pid */
/* (ptr[14] << 8) | ptr[15] - pcr pid */
tvhdebug(mt->mt_name, " service %04X (%d) onid %04X (%d) tsid %04X (%d)",
service_id, service_id, onid, onid, tsid, tsid);
/* Initialise the loop */
DVB_LOOP_INIT(ptr, len, 16, lptr, llen);
/* Find service */
s = mpegts_service_find(mm, service_id, 0, 1, &save);
charset = dvb_charset_find(mn, mm, s);
if (bq && s)
bouquet_add_service(bq, (service_t *)s);
/* Descriptor loop */
DVB_DESC_EACH(lptr, llen, dtag, dlen, dptr) {
tvhtrace(mt->mt_name, " dtag %02X dlen %d", dtag, dlen);
switch (dtag) {
case DVB_DESC_SERVICE:
if (dvb_desc_service(dptr, dlen, &stype, sprov,
sizeof(sprov), sname, sizeof(sname), charset))
return -1;
break;
case DVB_DESC_LOCAL_CHAN:
/* Find existing mux */
LIST_FOREACH(mux, &mn->mn_muxes, mm_network_link)
if (mux->mm_onid == onid && mux->mm_tsid == tsid)
break;
if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
return -1;
break;
case DVB_DESC_SAT_DEL:
mux = dvb_desc_sat_del(mm, onid, tsid, dptr, dlen);
if (mux) {
mpegts_mux_set_onid(mux, onid);
mpegts_mux_set_tsid(mux, tsid, 0);
}
break;
}
}
tvhtrace(mt->mt_name, " type %d name [%s] provider [%s]",
stype, sname, sprov);
/* Update service type */
if (stype && !s->s_dvb_servicetype) {
int r;
s->s_dvb_servicetype = stype;
save = 1;
tvhtrace(mt->mt_name, " type changed");
/* Set tvh service type */
if ((r = dvb_servicetype_lookup(stype)) != -1)
s->s_servicetype = r;
}
/* Update name */
if (*sname && strcmp(s->s_dvb_svcname ?: "", sname)) {
if (!s->s_dvb_svcname) {
tvh_str_update(&s->s_dvb_svcname, sname);
save = 1;
tvhtrace(mt->mt_name, " name changed");
}
}
/* Update provider */
if (*sprov && strcmp(s->s_dvb_provider ?: "", sprov)) {
if (!s->s_dvb_provider) {
tvh_str_update(&s->s_dvb_provider, sprov);
save = 1;
tvhtrace(mt->mt_name, " provider changed");
}
}
if (save) {
/* Update nice name */
pthread_mutex_lock(&s->s_stream_mutex);
service_make_nicename((service_t*)s);
pthread_mutex_unlock(&s->s_stream_mutex);
tvhdebug(mt->mt_name, " nicename %s", s->s_nicename);
/* Save changes */
idnode_changed(&s->s_id);
service_refresh_channel((service_t*)s);
}
}
if (bq && bq->bq_saveflag)
bouquet_save(bq, 1);
/* End */
return dvb_table_end(mt, st, sect);
}
#endif
/**
* PMT update reason flags
*/
@ -1589,6 +1772,18 @@ psi_tables_default ( mpegts_mux_t *mm )
NULL, "cat", MT_QUICKREQ | MT_CRC, DVB_CAT_PID);
}
#if ENABLE_MPEGTS_DVB
static void
psi_tables_dvb_fastscan( void *aux, bouquet_t *bq, const char *name, int pid )
{
tvhtrace("fastscan", "adding table %04X (%i) for '%s'", pid, pid, name);
mpegts_table_add(aux, DVB_FASTSCAN_NIT_BASE, DVB_FASTSCAN_MASK,
dvb_nit_callback, bq, "fs_nit", MT_CRC, pid);
mpegts_table_add(aux, DVB_FASTSCAN_SDT_BASE, DVB_FASTSCAN_MASK,
dvb_fs_sdt_callback, bq, "fs_sdt", MT_CRC, pid);
}
#endif
void
psi_tables_dvb ( mpegts_mux_t *mm )
{
@ -1599,6 +1794,17 @@ psi_tables_dvb ( mpegts_mux_t *mm )
DVB_SDT_PID);
mpegts_table_add(mm, DVB_BAT_BASE, DVB_BAT_MASK, dvb_bat_callback,
NULL, "bat", MT_CRC, DVB_BAT_PID);
#if ENABLE_MPEGTS_DVB
if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class)) {
dvb_mux_conf_t *mc = &((dvb_mux_t *)mm)->lm_tuning;
if (mc->dmc_fe_type == DVB_TYPE_S) {
int pos = mc->u.dmc_fe_qpsk.orbital_pos;
if (mc->u.dmc_fe_qpsk.orbital_dir == 'W')
pos = -pos;
dvb_fastscan_each(mm, pos, mc->dmc_fe_freq, psi_tables_dvb_fastscan);
}
}
#endif
}
void

View file

@ -382,7 +382,7 @@ mpegts_network_set_network_name
{
char buf[256];
if (mn->mn_network_name) return 0;
if (!name || !strcmp(name, mn->mn_network_name ?: ""))
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));

View file

@ -69,6 +69,7 @@
#include "libav.h"
#endif
#include "profile.h"
#include "bouquet.h"
#ifdef PLATFORM_LINUX
#include <sys/prctl.h>
@ -813,6 +814,8 @@ main(int argc, char **argv)
http_client_init(opt_user_agent);
esfilter_init();
bouquet_init();
service_init();
#if ENABLE_MPEGTS
@ -821,6 +824,8 @@ main(int argc, char **argv)
channel_init();
bouquet_service_resolve();
subscription_init();
dvr_config_init();
@ -917,6 +922,7 @@ main(int argc, char **argv)
tvhftrace("main", service_mapper_done);
tvhftrace("main", service_done);
tvhftrace("main", channel_done);
tvhftrace("main", bouquet_done);
tvhftrace("main", dvr_done);
tvhftrace("main", subscription_done);
tvhftrace("main", access_done);

View file

@ -46,6 +46,7 @@
#include "input.h"
#include "access.h"
#include "esfilter.h"
#include "bouquet.h"
static void service_data_timeout(void *aux);
static void service_class_delete(struct idnode *self);
@ -799,6 +800,8 @@ service_destroy(service_t *t, int delconf)
t->s_status = SERVICE_ZOMBIE;
bouquet_destroy_by_service(t);
TAILQ_INIT(&t->s_filt_components);
while((st = TAILQ_FIRST(&t->s_components)) != NULL)
service_stream_destroy(t, st);

View file

@ -443,6 +443,11 @@ typedef struct service {
int64_t s_current_pts;
/*
*
*/
void *s_master_bouquet;
} service_t;

View file

@ -6,10 +6,9 @@ tvheadend.cteditor = function(panel, index)
{
tvheadend.idnode_grid(panel, {
url: 'api/channeltag',
comet: 'channeltag',
titleS: 'Channel Tag',
titleP: 'Channel Tags',
iconCls: 'channelTags',
iconCls: 'channelTags',
tabIndex: index,
add: {
url: 'api/channeltag',
@ -27,3 +26,31 @@ tvheadend.cteditor = function(panel, index)
return panel;
};
/*
* Bouquet
*/
tvheadend.bouquet = function(panel, index)
{
var list = 'enabled,name,source,services_count,comment,lcn_off';
tvheadend.idnode_grid(panel, {
url: 'api/bouquet',
titleS: 'Bouquet',
titleP: 'Bouquets',
iconCls: 'bouquets',
tabIndex: index,
list: list,
del: true,
edit: { params: { list: list } },
sort: {
field: 'name',
direction: 'ASC'
},
help: function() {
new tvheadend.help('Bouquets', 'config_bouquet.html');
},
});
return panel;
};

View file

@ -366,6 +366,7 @@ function accessUpdate(o) {
});
tvheadend.channel_tab(chepg);
tvheadend.cteditor(chepg);
tvheadend.bouquet(chepg);
tvheadend.epggrab(chepg);
cp.add(chepg);