From 7134315e0f73eb9a943b8f6a5bab88aea48a33d7 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 29 Oct 2014 17:01:11 +0100 Subject: [PATCH] bouquet,fastscan: initial implementation --- Makefile | 7 +- data/conf/fastscan | 50 ++++ src/api.c | 1 + src/api.h | 1 + src/api/api_bouquet.c | 70 ++++++ src/api/api_channel.c | 6 +- src/api/api_service.c | 2 +- src/bouquet.c | 387 ++++++++++++++++++++++++++++++ src/bouquet.h | 88 +++++++ src/idnode.c | 19 +- src/idnode.h | 5 +- src/input/mpegts.c | 5 + src/input/mpegts.h | 4 +- src/input/mpegts/dvb.h | 6 + src/input/mpegts/dvb_psi.c | 224 ++++++++++++++++- src/input/mpegts/mpegts_network.c | 2 +- src/main.c | 6 + src/service.c | 3 + src/service.h | 5 + src/webui/static/app/cteditor.js | 31 ++- src/webui/static/app/tvheadend.js | 1 + 21 files changed, 899 insertions(+), 24 deletions(-) create mode 100644 data/conf/fastscan create mode 100644 src/api/api_bouquet.c create mode 100644 src/bouquet.c create mode 100644 src/bouquet.h diff --git a/Makefile b/Makefile index 5cde334c..2e497792 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/data/conf/fastscan b/data/conf/fastscan new file mode 100644 index 00000000..3c92b10d --- /dev/null +++ b/data/conf/fastscan @@ -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 + } +] diff --git a/src/api.c b/src/api.c index 8594706a..b089b9b7 100644 --- a/src/api.c +++ b/src/api.c @@ -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(); diff --git a/src/api.h b/src/api.h index 4928484d..e5b360d2 100644 --- a/src/api.h +++ b/src/api.h @@ -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 ); diff --git a/src/api/api_bouquet.c b/src/api/api_bouquet.c new file mode 100644 index 00000000..bd3e5e9e --- /dev/null +++ b/src/api/api_bouquet.c @@ -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 . + */ + +#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__ */ diff --git a/src/api/api_channel.c b/src/api/api_channel.c index aaf87b45..58ffe6bb 100644 --- a/src/api/api_channel.c +++ b/src/api/api_channel.c @@ -17,8 +17,8 @@ * along with this program. If not, see . */ -#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__ */ diff --git a/src/api/api_service.c b/src/api/api_service.c index 38012e42..f0bd3909 100644 --- a/src/api/api_service.c +++ b/src/api/api_service.c @@ -199,4 +199,4 @@ void api_service_init ( void ) } -#endif /* __TVH_API_IDNODE_H__ */ +#endif /* __TVH_API_SERVICE_H__ */ diff --git a/src/bouquet.c b/src/bouquet.c new file mode 100644 index 00000000..0881f91b --- /dev/null +++ b/src/bouquet.c @@ -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 . + */ + +#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); +} diff --git a/src/bouquet.h b/src/bouquet.h new file mode 100644 index 00000000..2e2537a5 --- /dev/null +++ b/src/bouquet.h @@ -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 . + */ + +#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_ */ diff --git a/src/idnode.c b/src/idnode.c index e9d52c61..bf60af44 100644 --- a/src/idnode.c +++ b/src/idnode.c @@ -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 ) diff --git a/src/idnode.h b/src/idnode.h index fbaae48f..544e6f91 100644 --- a/src/idnode.h +++ b/src/idnode.h @@ -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__ */ diff --git a/src/input/mpegts.c b/src/input/mpegts.c index 7a105bb8..4b65b89b 100644 --- a/src/input/mpegts.c +++ b/src/input/mpegts.c @@ -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(); } /****************************************************************************** diff --git a/src/input/mpegts.h b/src/input/mpegts.h index 55f544d7..57a5c047 100644 --- a/src/input/mpegts.h +++ b/src/input/mpegts.h @@ -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; diff --git a/src/input/mpegts/dvb.h b/src/input/mpegts/dvb.h index 2aee8a39..2f6a7337 100644 --- a/src/input/mpegts/dvb.h +++ b/src/input/mpegts/dvb.h @@ -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 diff --git a/src/input/mpegts/dvb_psi.c b/src/input/mpegts/dvb_psi.c index 67d35c92..8f8a7abd 100644 --- a/src/input/mpegts/dvb_psi.c +++ b/src/input/mpegts/dvb_psi.c @@ -24,6 +24,8 @@ #include "lang_codes.h" #include "service.h" #include "dvb_charset.h" +#include "bouquet.h" +#include "fastscan.h" #include #include @@ -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, §, &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, §, &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 diff --git a/src/input/mpegts/mpegts_network.c b/src/input/mpegts/mpegts_network.c index b43d10f3..e32d008d 100644 --- a/src/input/mpegts/mpegts_network.c +++ b/src/input/mpegts/mpegts_network.c @@ -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)); diff --git a/src/main.c b/src/main.c index 6bbf105e..a193bab6 100644 --- a/src/main.c +++ b/src/main.c @@ -69,6 +69,7 @@ #include "libav.h" #endif #include "profile.h" +#include "bouquet.h" #ifdef PLATFORM_LINUX #include @@ -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); diff --git a/src/service.c b/src/service.c index 1edabe82..415748fc 100644 --- a/src/service.c +++ b/src/service.c @@ -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); diff --git a/src/service.h b/src/service.h index 3097cb30..226a6251 100644 --- a/src/service.h +++ b/src/service.h @@ -443,6 +443,11 @@ typedef struct service { int64_t s_current_pts; + /* + * + */ + void *s_master_bouquet; + } service_t; diff --git a/src/webui/static/app/cteditor.js b/src/webui/static/app/cteditor.js index 79e2e96a..89560c34 100644 --- a/src/webui/static/app/cteditor.js +++ b/src/webui/static/app/cteditor.js @@ -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; +}; diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 2348fd5c..08834e8e 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -366,6 +366,7 @@ function accessUpdate(o) { }); tvheadend.channel_tab(chepg); tvheadend.cteditor(chepg); + tvheadend.bouquet(chepg); tvheadend.epggrab(chepg); cp.add(chepg);