diff --git a/Makefile b/Makefile index 07eb7e9e..f3f86149 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ SRCS += pvr.c pvr_rec.c SRCS += epg.c epg_xmltv.c +SRCS += dvb_support.c dvb_pmt.c dvb_dvr.c + SRCS += input_dvb.c input_iptv.c #input_v4l.c SRCS += output_client.c output_multicast.c diff --git a/channels.c b/channels.c index 35daa6db..1db448ef 100644 --- a/channels.c +++ b/channels.c @@ -28,9 +28,6 @@ #include #include -#include -#include - #include #include "tvhead.h" @@ -38,37 +35,35 @@ #include "input_v4l.h" #include "input_iptv.h" +#include "dvb_pmt.h" #include "channels.h" #include "transports.h" struct th_channel_queue channels; -static int channel_tally; +struct th_transport_list all_transports; +static int chtally; -#define MAXCHANNELS 256 - -static th_channel_t *charray[MAXCHANNELS]; -static int numchannels; +void scanner_init(void); th_channel_t * channel_find(const char *name, int create) { th_channel_t *ch; - TAILQ_FOREACH(ch, &channels, ch_global_link) { - if(!strcmp(name, ch->ch_name)) + TAILQ_FOREACH(ch, &channels, ch_global_link) + if(!strcasecmp(name, ch->ch_name)) return ch; - } + if(create == 0) return NULL; ch = calloc(1, sizeof(th_channel_t)); ch->ch_name = strdup(name); - TAILQ_INSERT_TAIL(&channels, ch, ch_global_link); - ch->ch_index = channel_tally++; - ch->ch_ref = ++reftally; + ch->ch_index = ++chtally; + TAILQ_INIT(&ch->ch_epg_events); - charray[ch->ch_index] = ch; - numchannels++; + TAILQ_INSERT_TAIL(&channels, ch, ch_global_link); + ch->ch_tag = tag_get(); return ch; } @@ -80,15 +75,39 @@ transportcmp(th_transport_t *a, th_transport_t *b) } +int +transport_set_channel(th_transport_t *t, const char *name) +{ + th_channel_t *ch; + th_pid_t *tp; + + if(LIST_FIRST(&t->tht_pids) == NULL) + return -1; + + if(t->tht_channel != NULL) + return 0; + + ch = channel_find(name, 1); + t->tht_channel = ch; + LIST_INSERT_SORTED(&ch->ch_transports, t, tht_channel_link, transportcmp); + + syslog(LOG_DEBUG, "Added service \"%s\" for channel \"%s\"", + t->tht_name, ch->ch_name); + LIST_FOREACH(tp, &t->tht_pids, tp_link) + syslog(LOG_DEBUG, " Pid %5d [%s]", + tp->tp_pid, htstvstreamtype2txt(tp->tp_type)); + + return 0; +} + + + static void service_load(struct config_head *head) { const char *name, *v; - pidinfo_t pids[10]; - int i, npids = 0; th_transport_t *t; - pidinfo_t *pi; th_channel_t *ch; if((name = config_get_str_sub(head, "channel", NULL)) == NULL) @@ -125,59 +144,42 @@ service_load(struct config_head *head) return; } - transport_monitor_init(t); + if((v = config_get_str_sub(head, "service_id", NULL)) != NULL) + t->tht_dvb_service_id = strtol(v, NULL, 0); - if(t->tht_npids == 0) { + if((v = config_get_str_sub(head, "network_id", NULL)) != NULL) + t->tht_dvb_network_id = strtol(v, NULL, 0); - if((v = config_get_str_sub(head, "video", NULL)) != NULL) { - pids[npids].pid = strtol(v, NULL, 0); - pids[npids++].type = HTSTV_MPEG2VIDEO; - } + if((v = config_get_str_sub(head, "transport_id", NULL)) != NULL) + t->tht_dvb_transport_id = strtol(v, NULL, 0); - if((v = config_get_str_sub(head, "h264", NULL)) != NULL) { - pids[npids].pid = strtol(v, NULL, 0); - pids[npids++].type = HTSTV_H264; - } + if((v = config_get_str_sub(head, "video", NULL)) != NULL) + transport_add_pid(t, strtol(v, NULL, 0), HTSTV_MPEG2VIDEO); - if((v = config_get_str_sub(head, "audio", NULL)) != NULL) { - pids[npids].pid = strtol(v, NULL, 0); - pids[npids++].type = HTSTV_MPEG2AUDIO; - } + if((v = config_get_str_sub(head, "h264", NULL)) != NULL) + transport_add_pid(t, strtol(v, NULL, 0), HTSTV_H264); - if((v = config_get_str_sub(head, "ac3", NULL)) != NULL) { - pids[npids].pid = strtol(v, NULL, 0); - pids[npids++].type = HTSTV_AC3; - } + if((v = config_get_str_sub(head, "audio", NULL)) != NULL) + transport_add_pid(t, strtol(v, NULL, 0), HTSTV_MPEG2AUDIO); + + if((v = config_get_str_sub(head, "ac3", NULL)) != NULL) + transport_add_pid(t, strtol(v, NULL, 0), HTSTV_AC3); - if((v = config_get_str_sub(head, "teletext", NULL)) != NULL) { - pids[npids].pid = strtol(v, NULL, 0); - pids[npids++].type = HTSTV_TELETEXT; - } + if((v = config_get_str_sub(head, "teletext", NULL)) != NULL) + transport_add_pid(t, strtol(v, NULL, 0), HTSTV_TELETEXT); - t->tht_pids = calloc(1, npids * sizeof(pidinfo_t)); - - for(i = 0; i < npids; i++) { - pi = t->tht_pids + i; - - pi->pid = pids[i].pid; - pi->type = pids[i].type; - pi->demuxer_fd = -1; - pi->cc_valid = 0; - } - - t->tht_npids = npids; - } t->tht_prio = atoi(config_get_str_sub(head, "prio", "")); - syslog(LOG_DEBUG, "Added service \"%s\" for channel \"%s\"", - t->tht_name, ch->ch_name); + transport_set_channel(t, name); - t->tht_channel = ch; - LIST_INSERT_SORTED(&ch->ch_transports, t, tht_channel_link, transportcmp); + transport_monitor_init(t); + LIST_INSERT_HEAD(&all_transports, t, tht_global_link); } + + static void channel_load(struct config_head *head) { @@ -218,21 +220,13 @@ channels_load(void) th_channel_t * -channel_by_id(unsigned int id) +channel_by_index(uint32_t index) { - return id < MAXCHANNELS ? charray[id] : NULL; -} - - -int -id_by_channel(th_channel_t *ch) -{ - return ch->ch_index; -} - - -int -channel_get_channels(void) -{ - return numchannels; + th_channel_t *ch; + + TAILQ_FOREACH(ch, &channels, ch_global_link) + if(ch->ch_index == index) + return ch; + + return NULL; } diff --git a/channels.h b/channels.h index 5b87c085..a2810720 100644 --- a/channels.h +++ b/channels.h @@ -19,9 +19,11 @@ #ifndef CHANNELS_H #define CHANNELS_H +extern struct th_channel_queue channels; + void channels_load(void); -th_channel_t *channel_by_id(unsigned int id); +th_channel_t *channel_by_index(uint32_t id); int id_by_channel(th_channel_t *ch); diff --git a/dispatch.c b/dispatch.c index 72a3ce05..b33a06ea 100644 --- a/dispatch.c +++ b/dispatch.c @@ -1,5 +1,5 @@ /* - * socket fd dispathcer + * socket fd dispatcher and (very simple) timer handling * Copyright (C) 2007 Andreas Öman * * This program is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -28,7 +29,6 @@ #include "dispatch.h" - static int epoll_fd; typedef struct epoll_entry { @@ -39,20 +39,100 @@ typedef struct epoll_entry { } epoll_entry_t; + +typedef struct stimer { + LIST_ENTRY(stimer) link; + void (*callback)(void *aux); + void *aux; + time_t t; +} stimer_t; + +LIST_HEAD(, stimer) dispatch_timers; + + +static int +stimercmp(stimer_t *a, stimer_t *b) +{ + return a->t - b->t; +} + + +void * +stimer_add(void (*callback)(void *aux), void *aux, int delta) +{ + time_t now; + stimer_t *ti = malloc(sizeof(stimer_t)); + + time(&now); + + ti->t = now + delta; + ti->aux = aux; + ti->callback = callback; + + LIST_INSERT_SORTED(&dispatch_timers, ti, link, stimercmp); + return ti; +} + +void +stimer_del(void *handle) +{ + stimer_t *ti = handle; + LIST_REMOVE(ti, link); + free(ti); +} + + +static int +stimer_next(void) +{ + stimer_t *ti = LIST_FIRST(&dispatch_timers); + struct timeval tv; + int64_t next, now, delta; + + if(ti == NULL) + return -1; + + next = (uint64_t)ti->t * 1000000ULL; + + gettimeofday(&tv, NULL); + + now = (uint64_t)tv.tv_sec * 1000000ULL + (uint64_t)tv.tv_usec; + + delta = next - now; + return delta < 0 ? 0 : delta / 1000ULL; +} + + + + + + + +static void +stimer_dispatch(time_t now) +{ + stimer_t *ti; + + while((ti = LIST_FIRST(&dispatch_timers)) != NULL) { + if(ti->t > now) + break; + + LIST_REMOVE(ti, link); + ti->callback(ti->aux); + free(ti); + } +} + + int dispatch_init(void) { - int fdflag; - epoll_fd = epoll_create(100); if(epoll_fd == -1) { perror("epoll_create()"); exit(1); } - fdflag = fcntl(epoll_fd, F_GETFD); - fdflag |= FD_CLOEXEC; - fcntl(epoll_fd, F_SETFD, fdflag); return 0; } @@ -105,53 +185,19 @@ dispatch_clr(void *handle, int flags) -void +int dispatch_delfd(void *handle) { struct epoll_entry *e = handle; - - epoll_ctl(epoll_fd, EPOLL_CTL_DEL, e->fd, &e->event); + int fd = e->fd; + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &e->event); free(e); + return fd; } -typedef struct dtimer { - LIST_ENTRY(dtimer) link; - void (*callback)(void *aux); - void *aux; -} dtimer_t; - -LIST_HEAD(, dtimer) dispatcher_timers; - -void * -dispatch_add_1sec_event(void (*callback)(void *aux), void *aux) -{ - dtimer_t *ti = malloc(sizeof(dtimer_t)); - LIST_INSERT_HEAD(&dispatcher_timers, ti, link); - ti->callback = callback; - ti->aux = aux; - return ti; -} - -void -dispatch_del_1sec_event(void *p) -{ - dtimer_t *ti = p; - LIST_REMOVE(ti, link); - free(ti); -} - -static void -run_1sec_events(void) -{ - dtimer_t *ti; - LIST_FOREACH(ti, &dispatcher_timers, link) - ti->callback(ti->aux); -} - - #define EPOLL_FDS_PER_ROUND 100 @@ -162,17 +208,14 @@ dispatcher(void) { struct epoll_entry *e; int i, n; - time_t now; static struct epoll_event events[EPOLL_FDS_PER_ROUND]; - n = epoll_wait(epoll_fd, events, EPOLL_FDS_PER_ROUND, 1000); + n = epoll_wait(epoll_fd, events, EPOLL_FDS_PER_ROUND, stimer_next()); - time(&now); - if(now != dispatch_clock) { - run_1sec_events(); - dispatch_clock = now; - } + time(&dispatch_clock); + stimer_dispatch(dispatch_clock); + for(i = 0; i < n; i++) { e = events[i].data.ptr; diff --git a/dispatch.h b/dispatch.h index 6b132321..b1e82d05 100644 --- a/dispatch.h +++ b/dispatch.h @@ -27,12 +27,12 @@ int dispatch_init(void); void *dispatch_addfd(int fd, void (*callback)(int events, void *opaque, int fd), void *opaque, int flags); -void dispatch_delfd(void *handle); +int dispatch_delfd(void *handle); void dispatch_set(void *handle, int flags); void dispatch_clr(void *handle, int flags); void dispatcher(void); -void *dispatch_add_1sec_event(void (*callback)(void *aux), void *aux); -void dispatch_del_1sec_event(void *p); +void *stimer_add(void (*callback)(void *aux), void *aux, int delta); +void stimer_del(void *handle); #endif /* DISPATCH_H */ diff --git a/dvb_dvr.c b/dvb_dvr.c new file mode 100644 index 00000000..78b5988e --- /dev/null +++ b/dvb_dvr.c @@ -0,0 +1,233 @@ +/* + * TV Input - Linux DVB interface - Feed receiver functions + * Copyright (C) 2007 Andreas Öman + * + * 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 + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include "tvhead.h" +#include "dispatch.h" +#include "input_dvb.h" +#include "dvb_dvr.h" +#include "channels.h" +#include "transports.h" +#include "dvb_support.h" + + +static void dvr_fd_callback(int events, void *opaque, int fd); + +int +dvb_dvr_init(th_dvb_adapter_t *tda) +{ + int dvr; + + dvr = open(tda->tda_dvr_path, O_RDONLY | O_NONBLOCK); + if(dvr == -1) { + syslog(LOG_ALERT, "%s: unable to open dvr\n", tda->tda_dvr_path); + return -1; + } + + dispatch_addfd(dvr, dvr_fd_callback, tda, DISPATCH_READ); + + + return 0; +} + +/* + * + */ +static void +dvb_dvr_process_packets(th_dvb_adapter_t *tda, uint8_t *tsb, int r) +{ + int pid; + th_transport_t *t; + + while(r >= 188) { + pid = (tsb[1] & 0x1f) << 8 | tsb[2]; + LIST_FOREACH(t, &tda->tda_transports, tht_adapter_link) + transport_recv_tsb(t, pid, tsb); + r -= 188; + tsb += 188; + } +} + + +/* + * + */ +static void +dvr_fd_callback(int events, void *opaque, int fd) +{ + th_dvb_adapter_t *tda = opaque; + uint8_t tsb0[188 * 10]; + int r; + + if(!(events & DISPATCH_READ)) + return; + + r = read(fd, tsb0, sizeof(tsb0)); + dvb_dvr_process_packets(tda, tsb0, r); +} + + + +/* + * + */ +static void +dvb_adapter_clean(th_dvb_adapter_t *tda) +{ + th_transport_t *t; + + while((t = LIST_FIRST(&tda->tda_transports)) != NULL) + dvb_stop_feed(t); +} + + + +/* + * + */ +void +dvb_stop_feed(th_transport_t *t) +{ + th_pid_t *tp; + + t->tht_dvb_adapter = NULL; + LIST_REMOVE(t, tht_adapter_link); + LIST_FOREACH(tp, &t->tht_pids, tp_link) { + close(tp->tp_demuxer_fd); + tp->tp_demuxer_fd = -1; + } + t->tht_status = TRANSPORT_IDLE; + transport_flush_subscribers(t); +} + + +/* + * + */ +int +dvb_start_feed(th_transport_t *t, unsigned int weight) +{ + th_dvb_adapter_t *tda, *cand = NULL; + struct dmx_pes_filter_params dmx_param; + th_pid_t *tp; + int w, fd, pid; + + LIST_FOREACH(tda, &dvb_adapters_running, tda_link) { + w = transport_compute_weight(&tda->tda_transports); + if(w < weight) + cand = tda; + + if(tda->tda_mux_current != NULL && + tda->tda_mux_current->tdmi_mux == t->tht_dvb_mux) + break; + } + + if(tda == NULL) { + if(cand == NULL) + return 1; + + dvb_adapter_clean(cand); + tda = cand; + } + + LIST_FOREACH(tp, &t->tht_pids, tp_link) { + + fd = open(tda->tda_demux_path, O_RDWR); + + pid = tp->tp_pid; + tp->tp_cc_valid = 0; + + if(fd == -1) { + tp->tp_demuxer_fd = -1; + syslog(LOG_ERR, + "\"%s\" unable to open demuxer \"%s\" for pid %d -- %s", + t->tht_name, tda->tda_demux_path, pid, strerror(errno)); + continue; + } + + memset(&dmx_param, 0, sizeof(dmx_param)); + dmx_param.pid = pid; + dmx_param.input = DMX_IN_FRONTEND; + dmx_param.output = DMX_OUT_TS_TAP; + dmx_param.pes_type = DMX_PES_OTHER; + dmx_param.flags = DMX_IMMEDIATE_START; + + if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) { + syslog(LOG_ERR, + "\"%s\" unable to configure demuxer \"%s\" for pid %d -- %s", + t->tht_name, tda->tda_demux_path, pid, strerror(errno)); + close(fd); + fd = -1; + } + + tp->tp_demuxer_fd = fd; + } + + LIST_INSERT_HEAD(&tda->tda_transports, t, tht_adapter_link); + t->tht_dvb_adapter = tda; + t->tht_status = TRANSPORT_RUNNING; + + dvb_tune(tda, t->tht_dvb_mux, 1); + return 0; +} + + +/* + * + */ + +int +dvb_configure_transport(th_transport_t *t, const char *muxname) +{ + th_dvb_mux_t *tdm; + + LIST_FOREACH(tdm, &dvb_muxes, tdm_global_link) + if(!strcmp(tdm->tdm_name, muxname)) + break; + + if(tdm == NULL) + return -1; + + t->tht_type = TRANSPORT_DVB; + t->tht_dvb_mux = tdm; + t->tht_name = strdup(tdm->tdm_title); + return 0; +} + + + + + + diff --git a/dvb_dvr.h b/dvb_dvr.h new file mode 100644 index 00000000..b101de80 --- /dev/null +++ b/dvb_dvr.h @@ -0,0 +1,28 @@ +/* + * TV Input - Linux DVB interface + * Copyright (C) 2007 Andreas Öman + * + * 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 DVB_DVR_H +#define DVB_DVR_H + +int dvb_dvr_init(th_dvb_adapter_t *tda); + +int dvb_start_feed(th_transport_t *t, unsigned int weight); + +void dvb_stop_feed(th_transport_t *t); + +#endif /* DVB_DVR_H */ diff --git a/dvb_pmt.c b/dvb_pmt.c new file mode 100644 index 00000000..0783b767 --- /dev/null +++ b/dvb_pmt.c @@ -0,0 +1,149 @@ +/* + * TV Input - DVB - Program Map Table parser + * Copyright (C) 2007 Andreas Öman + * + * 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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tvhead.h" +#include "transports.h" +#include "dvb_support.h" + +const char * +htstvstreamtype2txt(tv_streamtype_t s) +{ + switch(s) { + case HTSTV_MPEG2VIDEO: return "mpeg2video"; + case HTSTV_MPEG2AUDIO: return "mpeg2audio"; + case HTSTV_H264: return "h264"; + case HTSTV_AC3: return "AC-3"; + case HTSTV_TELETEXT: return "teletext"; + case HTSTV_SUBTITLES: return "subtitles"; + default: return ""; + } +} + + + + + + +int +dvb_parse_pmt(th_transport_t *t, uint8_t *ptr, int len) +{ + uint16_t pcr_pid, pid; + uint8_t estype; + int dllen; + uint8_t dtag, dlen; + uint16_t sid; + + tv_streamtype_t hts_stream_type; + + if(len < 9) + return -1; + + sid = ptr[0] << 8 | ptr[1]; + + pcr_pid = (ptr[5] & 0x1f) << 8 | ptr[6]; + dllen = (ptr[7] & 0xf) << 8 | ptr[8]; + + ptr += 9; + len -= 9; + + if(sid != t->tht_dvb_service_id) + return -1; + + + while(dllen > 2) { + dtag = ptr[0]; + dlen = ptr[1]; + + len -= 2; ptr += 2; dllen -= 2; + if(dlen > len) + break; + + len -= dlen; ptr += dlen; dllen -= dlen; + } + + while(len >= 5) { + estype = ptr[0]; + pid = (ptr[1] & 0x1f) << 8 | ptr[2]; + dllen = (ptr[3] & 0xf) << 8 | ptr[4]; + + ptr += 5; + len -= 5; + + hts_stream_type = 0; + + switch(estype) { + case 0x01: + case 0x02: + hts_stream_type = HTSTV_MPEG2VIDEO; + break; + + case 0x03: + case 0x04: + case 0x81: + hts_stream_type = HTSTV_MPEG2AUDIO; + break; + + case 0x1b: + hts_stream_type = HTSTV_H264; + break; + } + + while(dllen > 2) { + dtag = ptr[0]; + dlen = ptr[1]; + + len -= 2; ptr += 2; dllen -= 2; + if(dlen > len) + break; + + + switch(dtag) { + case DVB_DESC_TELETEXT: + if(estype == 0x06) + hts_stream_type = HTSTV_TELETEXT; + break; + + case DVB_DESC_SUBTITLE: + break; + + case DVB_DESC_AC3: + if(estype == 0x06) + hts_stream_type = HTSTV_AC3; + break; + } + len -= dlen; ptr += dlen; dllen -= dlen; + } + + if(hts_stream_type != 0) + transport_add_pid(t, pid, hts_stream_type); + } + return 0; +} + diff --git a/dvb_pmt.h b/dvb_pmt.h new file mode 100644 index 00000000..d168dda8 --- /dev/null +++ b/dvb_pmt.h @@ -0,0 +1,26 @@ +/* + * TV Input - Linux DVB interface + * Copyright (C) 2007 Andreas Öman + * + * 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 DVB_PMT_H +#define DVB_PMT_H + +int dvb_parse_pmt(th_transport_t *t, uint8_t *ptr, int len); + +const char *htstvstreamtype2txt(tv_streamtype_t s); + +#endif /* DVB_PMT_H */ diff --git a/dvb_support.c b/dvb_support.c new file mode 100644 index 00000000..060d55ec --- /dev/null +++ b/dvb_support.c @@ -0,0 +1,160 @@ +/* + * TV Input - DVB - Support functions + * Copyright (C) 2007 Andreas Öman + * + * 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 + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tvhead.h" +#include "dvb_support.h" + +/* + * DVB String conversion according to EN 300 468, Annex A + * Not all character sets are supported, but it should cover most of them + */ + +int +dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, + size_t srclen, const char *target_encoding) +{ + iconv_t ic; + const char *encoding; + char encbuf[20]; + int len; + char *in, *out; + size_t inlen, outlen; + int utf8 = 0; + int i; + unsigned char *tmp; + int r; + + if(srclen < 1) { + *dst = 0; + return 0; + } + + switch(src[0]) { + case 0: + return -1; + + case 0x01 ... 0x0b: + snprintf(encbuf, sizeof(encbuf), "ISO_8859-%d", src[0] + 4); + encoding = encbuf; + src++; srclen--; + break; + + case 0x0c ... 0x0f: + return -1; + + case 0x10: /* Table A.4 */ + if(srclen < 3 || src[1] != 0 || src[2] == 0 || src[2] > 0x0f) + return -1; + + snprintf(encbuf, sizeof(encbuf), "ISO_8859-%d", src[2]); + encoding = encbuf; + src+=3; srclen-=3; + break; + + case 0x11 ... 0x14: + return -1; + + case 0x15: + encoding = "UTF8"; + utf8 = 1; + break; + case 0x16 ... 0x1f: + return -1; + + default: + encoding = "LATIN1"; /* Default to latin-1 */ + break; + } + + if(srclen < 1) { + *dst = 0; + return 0; + } + + tmp = alloca(srclen + 1); + memcpy(tmp, src, srclen); + tmp[srclen] = 0; + + + /* Escape control codes */ + + if(!utf8) { + for(i = 0; i < srclen; i++) { + if(tmp[i] >= 0x80 && tmp[i] <= 0x9f) + tmp[i] = ' '; + } + } + + ic = iconv_open(target_encoding, encoding); + if(ic == (iconv_t) -1) { + return -1; + } + + inlen = srclen; + outlen = dstlen - 1; + + out = dst; + in = (char *)tmp; + + while(inlen > 0) { + r = iconv(ic, &in, &inlen, &out, &outlen); + + if(r == (size_t) -1) { + if(errno == EILSEQ) { + in++; + inlen--; + continue; + } + } + } + + iconv_close(ic); + len = dstlen - outlen - 1; + dst[len] = 0; + return 0; +} + + +int +dvb_get_string_with_len(char *dst, size_t dstlen, + const uint8_t *buf, size_t buflen, + const char *target_encoding) +{ + int l = buf[0]; + + if(l + 1 > buflen) + return -1; + + if(dvb_get_string(dst, dstlen, buf + 1, l, target_encoding)) + return -1; + + return l + 1; +} diff --git a/dvb_support.h b/dvb_support.h new file mode 100644 index 00000000..4fba114a --- /dev/null +++ b/dvb_support.h @@ -0,0 +1,47 @@ +/* + * TV Input - Linux DVB interface - Support + * Copyright (C) 2007 Andreas Öman + * + * 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 . + */ + +/* + * Based on: + * + * ITU-T Recommendation H.222.0 / ISO standard 13818-1 + * EN 300 468 - V1.7.1 + */ + +#ifndef DVB_SUPPORT_H +#define DVB_SUPPORT_H + + +/* Descriptors defined in EN 300 468 */ + +#define DVB_DESC_NETWORK_NAME 0x40 +#define DVB_DESC_SERVICE_LIST 0x41 +#define DVB_DESC_SHORT_EVENT 0x4d +#define DVB_DESC_SERVICE 0x48 +#define DVB_DESC_TELETEXT 0x56 +#define DVB_DESC_SUBTITLE 0x59 +#define DVB_DESC_AC3 0x6a + +int dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, + const size_t srclen, const char *target_encoding); + +int dvb_get_string_with_len(char *dst, size_t dstlen, + const uint8_t *buf, size_t buflen, + const char *target_encoding); + +#endif /* DVB_SUPPORT_H */ diff --git a/epg.c b/epg.c index 845fbfc6..929b9db0 100644 --- a/epg.c +++ b/epg.c @@ -25,66 +25,453 @@ #include "channels.h" #include "epg.h" +#define EPG_MAX_AGE 86400 -programme_t * -epg_find_programme(th_channel_t *ch, time_t start, time_t stop) +#define EPG_HASH_ID_WIDTH 256 + +static pthread_mutex_t epg_mutex = PTHREAD_MUTEX_INITIALIZER; +struct event_list epg_hash[EPG_HASH_ID_WIDTH]; + +void +epg_lock(void) { - th_proglist_t *tpl = &ch->ch_xmltv; - programme_t *pr; + pthread_mutex_lock(&epg_mutex); +} - /* XXX: use binary search */ - - LIST_FOREACH(pr, &tpl->tpl_programs, pr_link) { - if(pr->pr_start >= start && pr->pr_stop <= stop) - break; - } - return pr; +void +epg_unlock(void) +{ + pthread_mutex_unlock(&epg_mutex); } -programme_t * -epg_find_programme_by_time(th_channel_t *ch, time_t t) +void +epg_event_set_title(event_t *e, const char *title) { - th_proglist_t *tpl = &ch->ch_xmltv; - programme_t *pr; - - tpl = &ch->ch_xmltv; - /* XXX: use binary search */ + free((void *)e->e_title); + e->e_title = strdup(title); +} - LIST_FOREACH(pr, &tpl->tpl_programs, pr_link) { - if(t >= pr->pr_start && t < pr->pr_stop) - break; - } - return pr; +void +epg_event_set_desc(event_t *e, const char *desc) +{ + free((void *)e->e_desc); + e->e_desc = strdup(desc); } - - -programme_t * -epg_get_prog_by_id(th_channel_t *ch, unsigned int progid) +event_t * +epg_event_find_by_time0(struct event_queue *q, time_t start) { - th_proglist_t *tpl = &ch->ch_xmltv; + event_t *e; - if(progid >= tpl->tpl_nprograms) + TAILQ_FOREACH(e, q, e_link) + if(start >= e->e_start && start < e->e_start + e->e_duration) + break; + return e; +} + +event_t * +epg_event_find_by_time(th_channel_t *ch, time_t start) +{ + return epg_event_find_by_time0(&ch->ch_epg_events, start); +} + +event_t * +epg_event_find_by_tag(uint32_t tag) +{ + event_t *e; + unsigned int l = tag % EPG_HASH_ID_WIDTH; + + LIST_FOREACH(e, &epg_hash[l], e_hash_link) + if(e->e_tag == tag) + break; + + return e; +} + + +event_t * +epg_event_get_current(th_channel_t *ch) +{ + event_t *e; + + time_t now; + time(&now); + + e = ch->ch_epg_cur_event; + if(e == NULL || now < e->e_start || now > e->e_start + e->e_duration) return NULL; - return tpl->tpl_prog_vec[progid]; + return e; +} + +static int +startcmp(event_t *a, event_t *b) +{ + return a->e_start - b->e_start; +} + +event_t * +epg_event_build(struct event_queue *head, time_t start, int duration) +{ + time_t now; + event_t *e; + + time(&now); + + if(duration < 1 || start + duration < now - EPG_MAX_AGE) + return NULL; + + TAILQ_FOREACH(e, head, e_link) + if(start == e->e_start && duration == e->e_duration) + return e; + + e = calloc(1, sizeof(event_t)); + + e->e_duration = duration; + e->e_start = start; + TAILQ_INSERT_SORTED(head, e, e_link, startcmp); + + return e; } -programme_t * -epg_get_cur_prog(th_channel_t *ch) +void +epg_event_free(event_t *e) { - th_proglist_t *tpl = &ch->ch_xmltv; - programme_t *p = tpl->tpl_prog_current; + free((void *)e->e_title); + free((void *)e->e_desc); + free(e); +} + + + +static void +epg_event_destroy(th_channel_t *ch, event_t *e) +{ + // printf("epg: flushed event %s\n", e->e_title); + + if(ch->ch_epg_cur_event == e) + ch->ch_epg_cur_event = NULL; + + TAILQ_REMOVE(&ch->ch_epg_events, e, e_link); + LIST_REMOVE(e, e_hash_link); + + epg_event_free(e); +} + + +void +event_time_txt(time_t start, int duration, char *out, int outlen) +{ + char tmp1[40]; + char tmp2[40]; + char *c; + time_t stop = start + duration; + + ctime_r(&start, tmp1); + c = strchr(tmp1, '\n'); + if(c) + *c = 0; + + ctime_r(&stop, tmp2); + c = strchr(tmp2, '\n'); + if(c) + *c = 0; + + snprintf(out, outlen, "[%s - %s]", tmp1, tmp2); +} + + +static void +check_overlap0(th_channel_t *ch, event_t *a) +{ + char atime[100]; + char btime[100]; + event_t *b; + int overshot; + + b = TAILQ_NEXT(a, e_link); + if(b == NULL) + return; + + overshot = a->e_start + a->e_duration - b->e_start; + + if(overshot < 1) + return; + + event_time_txt(a->e_start, a->e_duration, atime, sizeof(atime)); + event_time_txt(b->e_start, b->e_duration, btime, sizeof(btime)); + + if(a->e_source > b->e_source) { + syslog(LOG_WARNING, + "\"%s\": Event \"%s\" %s with higest " + "precedence extends over \"%s\" %s", + ch->ch_name, a->e_title, atime, b->e_title, btime); + + b->e_start += overshot; + b->e_duration -= overshot; + + if(b->e_duration < 1) { + syslog(LOG_WARNING, + "\"%s\": Event \"%s\" destroyed", + ch->ch_name, b->e_title); + + epg_event_destroy(ch, b); + } else { + + syslog(LOG_WARNING, + "\"%s\": Event \"%s\" delayed and shortened by %ds", + ch->ch_name, b->e_title, overshot); + } + } else { + + syslog(LOG_WARNING, + "\"%s\": Event \"%s\" %s with higest " + "precedence extends over \"%s\" %s", + ch->ch_name, b->e_title, btime, a->e_title, atime); + + a->e_duration -= overshot; + + if(a->e_duration < 1) { + syslog(LOG_WARNING, + "\"%s\": Event \"%s\" destroyed", + ch->ch_name, a->e_title); + + epg_event_destroy(ch, a); + } else { + + syslog(LOG_WARNING, + "\"%s\": Event \"%s\" shortened by %ds", + ch->ch_name, a->e_title, overshot); + } + } +} + +static void +check_overlap(th_channel_t *ch, event_t *e) +{ + event_t *p; + + p = TAILQ_PREV(e, event_queue, e_link); + if(p != NULL) + check_overlap0(ch, p); + + check_overlap0(ch, e); +} + + + + +static void +epg_event_create(th_channel_t *ch, time_t start, int duration, + const char *title, const char *desc, int source, + uint16_t id) +{ + unsigned int l; + time_t now; + event_t *e; + + time(&now); + + if(duration < 1 || start + duration < now - EPG_MAX_AGE) + return; + + TAILQ_FOREACH(e, &ch->ch_epg_events, e_link) { + if(start == e->e_start && duration == e->e_duration) + break; + + if(start == e->e_start && !strcmp(e->e_title ?: "", title)) + break; + } + + if(e == NULL) { + + e = calloc(1, sizeof(event_t)); + + e->e_start = start; + TAILQ_INSERT_SORTED(&ch->ch_epg_events, e, e_link, startcmp); + + e->e_ch = ch; + e->e_event_id = id; + e->e_duration = duration; + + e->e_tag = tag_get(); + l = e->e_tag % EPG_HASH_ID_WIDTH; + LIST_INSERT_HEAD(&epg_hash[l], e, e_hash_link); + } + + if(source > e->e_source) { + + e->e_source = source; + + if(e->e_duration != duration) { + char before[100]; + char after[100]; + + event_time_txt(e->e_start, e->e_duration, before, sizeof(before)); + event_time_txt(e->e_start, duration, after, sizeof(after)); + + syslog(LOG_DEBUG, "\"%s\": \"%s\" %s changed duration to %s", + ch->ch_name, e->e_title ?: "", before, after); + e->e_duration = duration; + } + + if(title != NULL) epg_event_set_title(e, title); + if(desc != NULL) epg_event_set_desc(e, desc); + } + + check_overlap(ch, e); +} + + + + +void +epg_update_event_by_id(th_channel_t *ch, uint16_t event_id, + time_t start, int duration, const char *title, + const char *desc) +{ + event_t *e; + + TAILQ_FOREACH(e, &ch->ch_epg_events, e_link) + if(e->e_event_id == event_id) + break; + + if(e != NULL) { + /* We already have information about this event */ + + if(e->e_duration != duration || e->e_start != start) { + + char before[100]; + char after[100]; + + event_time_txt(e->e_start, e->e_duration, before, sizeof(before)); + event_time_txt(start, duration, after, sizeof(after)); + + syslog(LOG_DEBUG, "\"%s\": \"%s\" (serviceid = 0x04x) " + "%s changed duration to %s", + ch->ch_name, e->e_title ?: "", + before, after); + + TAILQ_REMOVE(&ch->ch_epg_events, e, e_link); + + e->e_start = start; + TAILQ_INSERT_SORTED(&ch->ch_epg_events, e, e_link, startcmp); + + check_overlap(ch, e); + } + + epg_event_set_title(e, title); + epg_event_set_desc(e, desc); + + } else { + + epg_event_create(ch, start, duration, title, desc, + EVENT_SRC_DVB, event_id); + } +} + + + + +static void +epg_locate_current_event(th_channel_t *ch, time_t now) +{ + ch->ch_epg_cur_event = epg_event_find_by_time(ch, now); +} + + + +static void +epg_channel_maintain(void) +{ + th_channel_t *ch; + event_t *e; time_t now; time(&now); - if(p == NULL || p->pr_stop < now || p->pr_start > now) - return NULL; + TAILQ_FOREACH(ch, &channels, ch_global_link) { - return p; + e = TAILQ_FIRST(&ch->ch_epg_events); + if(e != NULL && e->e_start + e->e_duration < now - EPG_MAX_AGE) + epg_event_destroy(ch, e); + + e = ch->ch_epg_cur_event; + if(e == NULL) { + epg_locate_current_event(ch, now); + continue; + } + + if(now >= e->e_start && now < e->e_start + e->e_duration) + continue; + + e = TAILQ_NEXT(e, e_link); + if(now >= e->e_start && now < e->e_start + e->e_duration) { + ch->ch_epg_cur_event = e; + continue; + } + + epg_locate_current_event(ch, now); + } } + + +/* + * + */ + +void +epg_transfer_events(th_channel_t *ch, struct event_queue *src, + const char *srcname) +{ + event_t *e; + int cnt = 0; + + epg_lock(); + + TAILQ_FOREACH(e, src, e_link) { + + epg_event_create(ch, e->e_start, e->e_duration, e->e_title, + e->e_desc, EVENT_SRC_XMLTV, 0); + cnt++; + } + epg_unlock(); + + syslog(LOG_DEBUG, + "Transfered %d events from \"%s\" to \"%s\"\n", + cnt, srcname, ch->ch_name); +} + + + + +/* + * + */ +static void * +epg_thread(void *aux) +{ + while(1) { + sleep(5); + epg_lock(); + epg_channel_maintain(); + epg_unlock(); + } +} + + + +/* + * + */ +void +epg_init(void) +{ + pthread_t ptid; + pthread_create(&ptid, NULL, epg_thread, NULL); +} + diff --git a/epg.h b/epg.h index dc9e1ebf..a88488f0 100644 --- a/epg.h +++ b/epg.h @@ -19,17 +19,33 @@ #ifndef EPG_H #define EPG_H -programme_t *epg_find_programme(th_channel_t *ch, - time_t start, time_t stop); +void epg_init(void); -programme_t *epg_find_programme_by_time(th_channel_t *ch, time_t t); +void epg_lock(void); -/* XXX: this is an ugly one */ +void epg_unlock(void); -programme_t *epg_get_prog_by_id(th_channel_t *ch, unsigned int progid); +event_t *epg_event_find_by_time0(struct event_queue *q, time_t start); -/* epg_get_cur_prog must be called while holding ch_prg_mutex */ +event_t *epg_event_find_by_time(th_channel_t *ch, time_t start); -programme_t *epg_get_cur_prog(th_channel_t *ch); +event_t *epg_event_find_by_tag(uint32_t id); + +event_t *epg_event_get_current(th_channel_t *ch); + +event_t *epg_event_build(struct event_queue *head, time_t start, int duration); + +void epg_event_free(event_t *e); + +void epg_event_set_title(event_t *e, const char *title); + +void epg_event_set_desc(event_t *e, const char *desc); + +void epg_update_event_by_id(th_channel_t *ch, uint16_t event_id, + time_t start, int duration, const char *title, + const char *desc); + +void epg_transfer_events(th_channel_t *ch, struct event_queue *src, + const char *srcname); #endif /* EPG_H */ diff --git a/epg_xmltv.c b/epg_xmltv.c index d8df6a1a..489c004d 100644 --- a/epg_xmltv.c +++ b/epg_xmltv.c @@ -33,11 +33,56 @@ #include "tvhead.h" #include "channels.h" +#include "epg.h" #include "epg_xmltv.h" #include "output_client.h" extern int xmltvreload; +LIST_HEAD(, xmltv_channel) xmltv_channel_list; + +typedef struct xmltv_map { + LIST_ENTRY(xmltv_map) xm_link; + th_channel_t *xm_channel; /* Set if we have resolved the channel */ + + int xm_isupdated; + +} xmltv_map_t; + + + + +typedef struct xmltv_channel { + LIST_ENTRY(xmltv_channel) xc_link; + const char *xc_name; + const char *xc_displayname; + const char *xc_icon; + + LIST_HEAD(, xmltv_map) xc_maps; + + struct event_queue xc_events; + +} xmltv_channel_t; + + +static xmltv_channel_t * +xc_find(const char *name) +{ + xmltv_channel_t *xc; + + LIST_FOREACH(xc, &xmltv_channel_list, xc_link) + if(!strcmp(xc->xc_name, name)) + return xc; + + xc = calloc(1, sizeof(xmltv_channel_t)); + xc->xc_name = strdup(name); + TAILQ_INIT(&xc->xc_events); + LIST_INSERT_HEAD(&xmltv_channel_list, xc, xc_link); + return xc; +} + + + #define XML_FOREACH(n) for(; (n) != NULL; (n) = (n)->next) @@ -45,56 +90,28 @@ static void xmltv_parse_channel(xmlNode *n, char *chid) { char *t, *c; - char *dname = NULL; - char *iname = NULL; - th_channel_t *tdc; - th_proglist_t *tpl; + xmltv_channel_t *xc; + + xc = xc_find(chid); XML_FOREACH(n) { c = (char *)xmlNodeGetContent(n); if(!strcmp((char *)n->name, "display-name")) { - if(dname != NULL) - free(dname); - dname = strdup(c); + free((void *)xc->xc_displayname); + xc->xc_displayname = strdup(c); } if(!strcmp((char *)n->name, "icon")) { t = (char *)xmlGetProp(n, (unsigned char *)"src"); - iname = t ? strdup(t) : NULL; + free((void *)xc->xc_icon); + xc->xc_icon = strdup(t); xmlFree(t); } xmlFree(c); } - - - if(dname != NULL) { - - TAILQ_FOREACH(tdc, &channels, ch_global_link) { - if(strcmp(tdc->ch_name, dname)) - continue; - - pthread_mutex_lock(&tdc->ch_epg_mutex); - tpl = &tdc->ch_xmltv; - - if(tpl->tpl_refname != NULL) - free((void *)tpl->tpl_refname); - tpl->tpl_refname = strdup(chid); - - if(tdc->ch_icon != NULL) - free((void *)tdc->ch_icon); - tdc->ch_icon = iname ? strdup(iname) : NULL; - - pthread_mutex_unlock(&tdc->ch_epg_mutex); - break; - } - free(dname); - } - - if(iname != NULL) - free(iname); } @@ -128,73 +145,38 @@ str2time(char *str) static void -xmltv_parse_programme(xmlNode *n, char *chid, char *start, char *stop) +xmltv_parse_programme(xmlNode *n, char *chid, char *startstr, char *stopstr) { char *c; - th_channel_t *tdc; - programme_t *pr, *t; - th_proglist_t *tpl; + event_t *e; + time_t start, stop; + int duration; + xmltv_channel_t *xc; - TAILQ_FOREACH(tdc, &channels, ch_global_link) { - pthread_mutex_lock(&tdc->ch_epg_mutex); - tpl = &tdc->ch_xmltv; - if(tpl->tpl_refname != NULL && !strcmp(tpl->tpl_refname, chid)) - break; - pthread_mutex_unlock(&tdc->ch_epg_mutex); - } + xc = xc_find(chid); + start = str2time(startstr); + stop = str2time(stopstr); - if(tdc == NULL) + duration = stop - start; + + e = epg_event_build(&xc->xc_events, start, duration); + if(e == NULL) return; - pr = calloc(1, sizeof(programme_t)); - - pr->pr_start = str2time(start); - pr->pr_stop = str2time(stop); - XML_FOREACH(n) { c = (char *)xmlNodeGetContent(n); if(!strcmp((char *)n->name, "title")) { - pr->pr_title = strdup(c); + epg_event_set_title(e, c); } else if(!strcmp((char *)n->name, "desc")) { - pr->pr_desc = strdup(c); + epg_event_set_desc(e, c); } xmlFree(c); } - - - LIST_FOREACH(t, &tpl->tpl_programs, pr_link) { - if(pr->pr_start == t->pr_start && - pr->pr_stop == t->pr_stop) - break; - } - - if(t != NULL) { - free(t->pr_title); - free(t->pr_desc); - - t->pr_title = pr->pr_title; - t->pr_desc = pr->pr_desc; - - free(pr); - - t->pr_delete_me = 0; - - } else { - - tpl->tpl_nprograms++; - - pr->pr_ref = ++reftally; - - pr->pr_ch = tdc; - LIST_INSERT_HEAD(&tpl->tpl_programs, pr, pr_link); - } - pthread_mutex_unlock(&tdc->ch_epg_mutex); } - static void xmltv_parse_tv(xmlNode *n) { @@ -240,233 +222,28 @@ xmltv_parse_root(xmlNode *n) /* - * Sorting functions * */ - -static int -xmltvpcmp(const void *A, const void *B) -{ - programme_t *a = *(programme_t **)A; - programme_t *b = *(programme_t **)B; - - return a->pr_start - b->pr_start; -} - - -static void -xmltv_sort_channel(th_proglist_t *tpl) -{ - programme_t *pr; - int i; - - if(tpl->tpl_prog_vec != NULL) - free(tpl->tpl_prog_vec); - - if(tpl->tpl_nprograms == 0) - return; - - tpl->tpl_prog_vec = malloc(tpl->tpl_nprograms * sizeof(void *)); - i = 0; - - LIST_FOREACH(pr, &tpl->tpl_programs, pr_link) - tpl->tpl_prog_vec[i++] = pr; - - qsort(tpl->tpl_prog_vec, tpl->tpl_nprograms, sizeof(void *), xmltvpcmp); - - for(i = 0; i < tpl->tpl_nprograms; i++) { - tpl->tpl_prog_vec[i]->pr_index = i; - } -} - - -static void -xmltv_insert_dummies(th_channel_t *ch, th_proglist_t *tpl) -{ - programme_t *pr2, *pr; - int i, delta = 0; - - time_t nu, *prev; - struct tm tm; - - if(tpl->tpl_nprograms < 1) - return; - - nu = tpl->tpl_prog_vec[0]->pr_start; - - localtime_r(&nu, &tm); - - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - - nu = mktime(&tm); - - prev = ν - - for(i = 0; i < tpl->tpl_nprograms - 1; i++) { - - nu = *prev; - - pr2 = tpl->tpl_prog_vec[i]; - - if(nu != pr2->pr_start) { - - pr = calloc(1, sizeof(programme_t)); - - pr->pr_start = nu; - pr->pr_stop = pr2->pr_start; - - pr->pr_title = NULL; - pr->pr_desc = NULL; - - delta++; - - pr->pr_ch = ch; - pr->pr_ref = ++reftally; - LIST_INSERT_HEAD(&tpl->tpl_programs, pr, pr_link); - } - prev = &pr2->pr_stop; - } - tpl->tpl_nprograms += delta; -} - - - -static void -xmltv_purge_channel(th_proglist_t *tpl) -{ - programme_t *pr, *t; - - for(pr = LIST_FIRST(&tpl->tpl_programs) ; pr != NULL; pr = t) { - t = LIST_NEXT(pr, pr_link); - - if(pr->pr_delete_me == 0) - continue; - - free(pr->pr_title); - free(pr->pr_desc); - - LIST_REMOVE(pr, pr_link); - free(pr); - tpl->tpl_nprograms--; - } -} - -static void -xmltv_prep_reload(void) -{ - programme_t *pr; - th_channel_t *tdc; - th_proglist_t *tpl; - - TAILQ_FOREACH(tdc, &channels, ch_global_link) { - pthread_mutex_lock(&tdc->ch_epg_mutex); - tpl = &tdc->ch_xmltv; - - LIST_FOREACH(pr, &tpl->tpl_programs, pr_link) - pr->pr_delete_me = 1; - pthread_mutex_unlock(&tdc->ch_epg_mutex); - } -} - - - -static void -xmltv_set_current(th_proglist_t *tpl) -{ - time_t now; - programme_t *pr; - programme_t **vec = tpl->tpl_prog_vec; - int i, len = tpl->tpl_nprograms; - - time(&now); - - pr = NULL; - - for(i = len - 1; i >= 0; i--) { - - pr = vec[i]; - if(pr->pr_start < now) - break; - } - - if(i == len || pr == NULL || pr->pr_start > now) { - tpl->tpl_prog_current = NULL; - } else { - tpl->tpl_prog_current = pr; - } -} - - -static void -xmltv_sort_programs(void) -{ - th_channel_t *tdc; - th_proglist_t *tpl; - - TAILQ_FOREACH(tdc, &channels, ch_global_link) { - - pthread_mutex_lock(&tdc->ch_epg_mutex); - - tpl = &tdc->ch_xmltv; - - if(tpl->tpl_refname != NULL) { - syslog(LOG_DEBUG, "xmltv: %s (%s) %d programs loaded", - tdc->ch_name, tpl->tpl_refname, tpl->tpl_nprograms); - - xmltv_purge_channel(tpl); - xmltv_sort_channel(tpl); - xmltv_insert_dummies(tdc, tpl); - xmltv_sort_channel(tpl); - xmltv_set_current(tpl); - } - - pthread_mutex_unlock(&tdc->ch_epg_mutex); - - clients_enq_ref(tdc->ch_ref); - } -} - - void -xmltv_update_current(void) +xmltv_flush(void) { - th_channel_t *tdc; - th_proglist_t *tpl; - programme_t *pr; - time_t now; - int idx; + xmltv_channel_t *xc; + xmltv_map_t *xm; + event_t *e; - time(&now); - - TAILQ_FOREACH(tdc, &channels, ch_global_link) { - - pthread_mutex_lock(&tdc->ch_epg_mutex); - tpl = &tdc->ch_xmltv; - - pr = tpl->tpl_prog_current; - - if(pr != NULL && pr->pr_stop < now) { - - clients_enq_ref(pr->pr_ref); - - idx = pr->pr_index + 1; - if(idx < tpl->tpl_nprograms) { - - pr = tpl->tpl_prog_vec[idx]; - tpl->tpl_prog_current = pr; - - clients_enq_ref(pr->pr_ref); - clients_enq_ref(tdc->ch_ref); - } + LIST_FOREACH(xc, &xmltv_channel_list, xc_link) { + while((e = TAILQ_FIRST(&xc->xc_events)) != NULL) { + TAILQ_REMOVE(&xc->xc_events, e, e_link); + epg_event_free(e); + } + + while((xm = LIST_FIRST(&xc->xc_maps)) != NULL) { + LIST_REMOVE(xm, xm_link); + free(xm); } - pthread_mutex_unlock(&tdc->ch_epg_mutex); } } - - /* * */ @@ -485,17 +262,109 @@ xmltv_load(void) return; } - xmltv_prep_reload(); + xmltv_flush(); root_element = xmlDocGetRootElement(doc); xmltv_parse_root(root_element); xmlFreeDoc(doc); xmlCleanupParser(); +} - xmltv_sort_programs(); +/* + * + */ +static int +xmltv_map(xmltv_channel_t *xc, th_channel_t *ch) +{ + xmltv_map_t *xm; + LIST_FOREACH(xm, &xc->xc_maps, xm_link) + if(xm->xm_channel == ch) + return -1; + + xm = calloc(1, sizeof(xmltv_map_t)); + xm->xm_channel = ch; + LIST_INSERT_HEAD(&xc->xc_maps, xm, xm_link); + return 0; } +/* + * + */ +static void +xmltv_resolve_by_events(xmltv_channel_t *xc) +{ + th_channel_t *ch; + event_t *ec, *ex; + time_t now; + int thres; + int i; + + time(&now); + + for(i = 0; i < 4; i++) { + now += 7200; + + ex = epg_event_find_by_time0(&xc->xc_events, now); + if(ex == NULL) + break; + + TAILQ_FOREACH(ch, &channels, ch_global_link) { + ec = epg_event_find_by_time0(&ch->ch_epg_events, now); + + thres = 10; + + while(1) { + if(ec == NULL || ex == NULL) + break; + + if(thres == 0) { + if(xmltv_map(xc, ch) == 0) + syslog(LOG_DEBUG, + "xmltv: Heuristically mapped \"%s\" (%s) to \"%s\"", + xc->xc_displayname, xc->xc_name, ch->ch_name); + break; + } + + if(ec->e_start != ex->e_start || ec->e_duration != ex->e_duration) + break; + + ec = TAILQ_NEXT(ec, e_link); + ex = TAILQ_NEXT(ex, e_link); + thres--; + } + } + } +} + + +/* + * + */ +void +xmltv_transfer(void) +{ + xmltv_channel_t *xc; + xmltv_map_t *xm; + th_channel_t *ch; + + LIST_FOREACH(xc, &xmltv_channel_list, xc_link) { + + ch = channel_find(xc->xc_displayname, 0); + if(ch != NULL) + xmltv_map(xc, ch); + + xmltv_resolve_by_events(xc); + + LIST_FOREACH(xm, &xc->xc_maps, xm_link) { + if(xm->xm_isupdated) + continue; + + epg_transfer_events(xm->xm_channel, &xc->xc_events, xc->xc_name); + xm->xm_isupdated = 1; + } + } +} /* @@ -514,7 +383,8 @@ xmltv_thread(void *aux) xmltv_load(); } - xmltv_update_current(); + xmltv_transfer(); + } } diff --git a/input_dvb.c b/input_dvb.c index 0120fc7e..64458ec2 100644 --- a/input_dvb.c +++ b/input_dvb.c @@ -41,27 +41,48 @@ #include "channels.h" #include "transports.h" #include "teletext.h" +#include "epg.h" +#include "dvb_support.h" +#include "dvb_pmt.h" +#include "dvb_dvr.h" -static struct th_dvb_adapter_list adapters; +struct th_dvb_mux_list dvb_muxes; +struct th_dvb_adapter_list dvb_adapters_probing; +struct th_dvb_adapter_list dvb_adapters_running; -static void dvr_fd_callback(int events, void *opaque, int fd); +static void dvb_tdt_add_demux(th_dvb_mux_instance_t *tdmi); +//static void dvb_nit_add_demux(th_dvb_mux_instance_t *tdmi); +static void dvb_eit_add_demux(th_dvb_mux_instance_t *tdmi); +static void dvb_sdt_add_demux(th_dvb_mux_instance_t *tdmi); -static void dvb_datetime_add_demux(th_dvb_adapter_t *tda); +static int dvb_tune_tdmi(th_dvb_mux_instance_t *tdmi, int maylog); + +static void *dvb_monitor_thread(void *aux); + +static void dvb_add_muxes(void); + +static void tdmi_check_scan_status(th_dvb_mux_instance_t *tdmi); + +static void dvb_start_initial_scan(th_dvb_mux_instance_t *tdmi); + +static void tdmi_activate(th_dvb_mux_instance_t *tdmi); static void dvb_add_adapter(const char *path) { char fname[256]; int fe; - int dvr; th_dvb_adapter_t *tda; + pthread_t ptid; snprintf(fname, sizeof(fname), "%s/frontend0", path); fe = open(fname, O_RDWR | O_NONBLOCK); - if(fe == -1) + if(fe == -1) { + if(errno != ENOENT) + syslog(LOG_ALERT, "Unable to open %s -- %s\n", fname, strerror(errno)); return; - + } tda = calloc(1, sizeof(th_dvb_adapter_t)); tda->tda_path = strdup(path); tda->tda_demux_path = malloc(256); @@ -70,40 +91,38 @@ dvb_add_adapter(const char *path) snprintf(tda->tda_dvr_path, 256, "%s/dvr0", path); - dvr = open(tda->tda_dvr_path, O_RDONLY | O_NONBLOCK); - if(dvr == -1) { - fprintf(stderr, "%s: unable to open dvr\n", fname); - free(tda); - return; - } - - tda->tda_fe_fd = fe; - tda->tda_dvr_fd = dvr; if(ioctl(tda->tda_fe_fd, FE_GET_INFO, &tda->tda_fe_info)) { - fprintf(stderr, "%s: Unable to query adapter\n", fname); + syslog(LOG_ALERT, "%s: Unable to query adapter\n", fname); close(fe); - close(dvr); free(tda); return; } + if(dvb_dvr_init(tda) < 0) { + close(fe); + free(tda); + return; + } + + LIST_INSERT_HEAD(&dvb_adapters_probing, tda, tda_link); + + tda->tda_name = strdup(tda->tda_fe_info.name); + pthread_create(&ptid, NULL, dvb_monitor_thread, tda); + syslog(LOG_INFO, "Adding adapter %s (%s)", tda->tda_fe_info.name, path); - tda->tda_name = strdup(tda->tda_fe_info.name); - LIST_INSERT_HEAD(&adapters, tda, tda_link); - dispatch_addfd(dvr, dvr_fd_callback, tda, DISPATCH_READ); - - dvb_datetime_add_demux(tda); } void -dvb_add_adapters(void) +dvb_init(void) { + th_dvb_adapter_t *tda; + th_dvb_mux_instance_t *tdmi; char path[200]; int i; @@ -111,231 +130,367 @@ dvb_add_adapters(void) snprintf(path, sizeof(path), "/dev/dvb/adapter%d", i); dvb_add_adapter(path); } -} -#if 0 -static void * -dvb_fe_loop(void *aux) -{ - th_dvb_adapter_t *tda = aux; - struct timespec ts; + dvb_add_muxes(); - while(1) { - - if(tda->tda_reconfigure == 0) { - - ts.tv_sec = time(NULL) + 1; - ts.tv_nsec = 0; - - pthread_cond_timedwait(&tda->tda_cond, &tda->tda_mutex, &ts); - } - - if(ioctl(tda->tda_fe_fd, FE_READ_STATUS, &tda->tda_fe_status) < 0) - tda->tda_fe_status = 0; - - if(ioctl(tda->tda_fe_fd, FE_READ_SIGNAL_STRENGTH, &tda->tda_signal) < 0) - tda->tda_signal = 0; - - if(ioctl(tda->tda_fe_fd, FE_READ_SNR, &tda->tda_snr) < 0) - tda->tda_snr = 0; - - if(ioctl(tda->tda_fe_fd, FE_READ_BER, &tda->tda_ber) < 0) - tda->tda_ber = 0; - - if(ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, - &tda->tda_uncorrected_blocks) < 0) - tda->tda_uncorrected_blocks = 0; - - if(tda->tda_reconfigure != 0) { - - if(ioctl(tda->tda_fe_fd, FE_SET_FRONTEND, &tda->tda_fe_params) < 0) { - perror("ioctl"); - } - sleep(1); - tda->tda_reconfigure = 0; - } - } -} -#endif - - - -static void -dvb_dvr_process_packets(th_dvb_adapter_t *tda, uint8_t *tsb, int r) -{ - int pid; - th_transport_t *t; - - while(r > 0) { - pid = (tsb[1] & 0x1f) << 8 | tsb[2]; - LIST_FOREACH(t, &tda->tda_transports, tht_adapter_link) - transport_recv_tsb(t, pid, tsb); - r -= 188; - tsb += 188; + LIST_FOREACH(tda, &dvb_adapters_probing, tda_link) { + tdmi = LIST_FIRST(&tda->tda_muxes_configured); + dvb_start_initial_scan(tdmi); } } + + + + static void -dvr_fd_callback(int events, void *opaque, int fd) +tdt_destroy(th_dvb_table_t *tdt) { - th_dvb_adapter_t *tda = opaque; - uint8_t tsb0[188 * 10]; - int r; + LIST_REMOVE(tdt, tdt_link); + close(dispatch_delfd(tdt->tdt_handle)); + free(tdt); +} + + +static void +tdmi_clean_tables(th_dvb_mux_instance_t *tdmi) +{ + th_dvb_table_t *tdt; + while((tdt = LIST_FIRST(&tdmi->tdmi_tables)) != NULL) + tdt_destroy(tdt); +} + + +static void +dvb_table_recv(int events, void *opaque, int fd) +{ + th_dvb_table_t *tdt = opaque; + uint8_t sec[4096], *ptr; + int r, len; + uint8_t tableid; if(!(events & DISPATCH_READ)) return; - r = read(fd, tsb0, sizeof(tsb0)); - dvb_dvr_process_packets(tda, tsb0, r); -} + r = read(fd, sec, sizeof(sec)); + if(r < 3) + return; + r -= 3; + tableid = sec[0]; + len = ((sec[1] & 0x0f) << 8) | sec[2]; + + if(len < r) + return; -void -dvb_stop_feed(th_transport_t *t) -{ - int i; + ptr = &sec[3]; - t->tht_dvb_adapter = NULL; - LIST_REMOVE(t, tht_adapter_link); - for(i = 0; i < t->tht_npids; i++) { - close(t->tht_pids[i].demuxer_fd); - t->tht_pids[i].demuxer_fd = -1; + if(!tdt->tdt_callback(tdt->tdt_tdmi, ptr, len, tableid, tdt->tdt_opaque)) { + tdt->tdt_count++; } - t->tht_status = TRANSPORT_IDLE; - transport_flush_subscribers(t); + tdmi_check_scan_status(tdt->tdt_tdmi); } + + + + static void -dvb_adapter_clean(th_dvb_adapter_t *tda) +tdt_add(th_dvb_mux_instance_t *tdmi, int fd, + int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, + uint8_t tableid, void *opaque), void *opaque, + int initial_count) { - th_transport_t *t; - - while((t = LIST_FIRST(&tda->tda_transports)) != NULL) - dvb_stop_feed(t); + th_dvb_table_t *tdt = malloc(sizeof(th_dvb_table_t)); + + LIST_INSERT_HEAD(&tdmi->tdmi_tables, tdt, tdt_link); + tdt->tdt_callback = callback; + tdt->tdt_opaque = opaque; + tdt->tdt_tdmi = tdmi; + tdt->tdt_handle = dispatch_addfd(fd, dvb_table_recv, tdt, DISPATCH_READ); + tdt->tdt_count = initial_count; } -int -dvb_start_feed(th_transport_t *t, unsigned int weight) + + + +static int +dvb_tune_tdmi(th_dvb_mux_instance_t *tdmi, int maylog) { - struct dvb_frontend_parameters *params; - th_dvb_adapter_t *tda, *cand = NULL; - struct dmx_pes_filter_params dmx_param; - int w, i, fd, pid; + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + th_dvb_mux_t *tdm = tdmi->tdmi_mux; + int i; - params = &t->tht_dvb_fe_params; - - LIST_FOREACH(tda, &adapters, tda_link) { - w = transport_compute_weight(&tda->tda_transports); - if(w < weight) - cand = tda; - - if(!memcmp(params, &tda->tda_fe_params, - sizeof(struct dvb_frontend_parameters))) - break; - } - - if(tda == NULL) { - if(cand == NULL) - return 1; - - dvb_adapter_clean(cand); - tda = cand; - } - - for(i = 0; i < t->tht_npids; i++) { - - fd = open(tda->tda_demux_path, O_RDWR); - pid = t->tht_pids[i].pid; - t->tht_pids[i].cc_valid = 0; - - if(fd == -1) { - fprintf(stderr, "Unable to open demux for pid %d\n", pid); - return -1; - } - - memset(&dmx_param, 0, sizeof(dmx_param)); - dmx_param.pid = pid; - dmx_param.input = DMX_IN_FRONTEND; - dmx_param.output = DMX_OUT_TS_TAP; - dmx_param.pes_type = DMX_PES_OTHER; - dmx_param.flags = DMX_IMMEDIATE_START; - - if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) { - syslog(LOG_ERR, "%s (%s) -- Cannot demux pid %d -- %s", - tda->tda_name, tda->tda_path, pid, strerror(errno)); - } - - t->tht_pids[i].demuxer_fd = fd; - } - - LIST_INSERT_HEAD(&tda->tda_transports, t, tht_adapter_link); - t->tht_dvb_adapter = tda; - t->tht_status = TRANSPORT_RUNNING; - - if(!memcmp(params, &tda->tda_fe_params, - sizeof(struct dvb_frontend_parameters))) + if(tda->tda_mux_current == tdmi) return 0; - memcpy(&tda->tda_fe_params, params, sizeof(struct dvb_frontend_parameters)); - - syslog(LOG_DEBUG, "%s (%s) tuning to transport \"%s\"", - tda->tda_name, tda->tda_path, t->tht_name); + if(tda->tda_mux_current != NULL) + tdmi_clean_tables(tda->tda_mux_current); - i = ioctl(tda->tda_fe_fd, FE_SET_FRONTEND, &tda->tda_fe_params); + tda->tda_mux_current = tdmi; + + if(maylog) + syslog(LOG_DEBUG, "%s (%s) tuning to mux \"%s\"", + tda->tda_name, tda->tda_path, tdmi->tdmi_mux->tdm_title); + + i = ioctl(tda->tda_fe_fd, FE_SET_FRONTEND, &tdm->tdm_fe_params); if(i != 0) { - syslog(LOG_ERR, "%s (%s) tuning to transport \"%s\"" - " -- Front configuration failed -- %s", - tda->tda_name, tda->tda_path, t->tht_name, - strerror(errno)); + if(maylog) + syslog(LOG_ERR, "%s (%s) tuning to transport \"%s\"" + " -- Front configuration failed -- %s", + tda->tda_name, tda->tda_path, tdm->tdm_title, + strerror(errno)); + return -1; } + + + dvb_tdt_add_demux(tdmi); + dvb_eit_add_demux(tdmi); + // dvb_nit_add_demux(tdmi); + dvb_sdt_add_demux(tdmi); + return 0; } -/* + + + +int +dvb_tune(th_dvb_adapter_t *tda, th_dvb_mux_t *tdm, int maylog) +{ + th_dvb_mux_instance_t *tdmi; + + LIST_FOREACH(tdmi, &tda->tda_muxes_active, tdmi_adapter_link) + if(tdmi->tdmi_mux == tdm) + break; + if(tdmi == NULL) + return -1; + + return dvb_tune_tdmi(tdmi, maylog); +} + + + + + + + + + + + + + + + + + +static void +dvb_add_mux_instance(th_dvb_adapter_t *tda, th_dvb_mux_t *tdm) +{ + th_dvb_mux_instance_t *tdmi; + + tdmi = calloc(1, sizeof(th_dvb_mux_instance_t)); + + tdmi->tdmi_status = TDMI_CONFIGURED; + + tdmi->tdmi_mux = tdm; + tdmi->tdmi_adapter = tda; + + LIST_INSERT_HEAD(&tda->tda_muxes_configured, tdmi, tdmi_adapter_link); + LIST_INSERT_HEAD(&tdm->tdm_instances, tdmi, tdmi_mux_link); +} + + + +static void +dvb_add_muxes(void) +{ + th_dvb_mux_t *tdm; + th_dvb_adapter_t *tda; + config_entry_t *ce; + const char *s; + struct dvb_frontend_parameters *fe_param; + char buf[100]; + + TAILQ_FOREACH(ce, &config_list, ce_link) { + if(ce->ce_type == CFG_SUB && !strcasecmp("dvbmux", ce->ce_key)) { + + if((s = config_get_str_sub(&ce->ce_sub, "name", NULL)) == NULL) + continue; + + tdm = calloc(1, sizeof(th_dvb_mux_t)); + fe_param = &tdm->tdm_fe_params; + + fe_param->inversion = INVERSION_AUTO; + fe_param->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; + fe_param->u.ofdm.code_rate_HP = FEC_2_3; + fe_param->u.ofdm.code_rate_LP = FEC_1_2; + fe_param->u.ofdm.constellation = QAM_64; + fe_param->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; + fe_param->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; + fe_param->u.ofdm.hierarchy_information = HIERARCHY_NONE; + + fe_param->frequency = + atoi(config_get_str_sub(&ce->ce_sub, "frequency", "0")); + + tdm->tdm_name = strdup(s); + + snprintf(buf, sizeof(buf), "DVB-T: %s (%.1f MHz)", s, + (float)fe_param->frequency / 1000000.0f); + tdm->tdm_title = strdup(buf); + + LIST_INSERT_HEAD(&dvb_muxes, tdm, tdm_global_link); + + LIST_FOREACH(tda, &dvb_adapters_probing, tda_link) { + dvb_add_mux_instance(tda, tdm); + } + } + } +} + + + + +static void +dvb_monitor_current_mux(th_dvb_adapter_t *tda) +{ + th_dvb_mux_instance_t *tdmi = tda->tda_mux_current; + + if(tdmi == NULL) + return; + + if(ioctl(tda->tda_fe_fd, FE_READ_STATUS, &tdmi->tdmi_fe_status) < 0) + tdmi->tdmi_fe_status = 0; + + if(tdmi->tdmi_fe_status & FE_HAS_LOCK) + tdmi->tdmi_status = NULL; + else if(tdmi->tdmi_fe_status & FE_HAS_SYNC) + tdmi->tdmi_status = "No lock, but sync ok"; + else if(tdmi->tdmi_fe_status & FE_HAS_VITERBI) + tdmi->tdmi_status = "No lock, but FEC stable"; + else if(tdmi->tdmi_fe_status & FE_HAS_CARRIER) + tdmi->tdmi_status = "No lock, but carrier present"; + else if(tdmi->tdmi_fe_status & FE_HAS_SIGNAL) + tdmi->tdmi_status = "No lock, but faint signal present"; + else + tdmi->tdmi_status = "No signal"; + + if(ioctl(tda->tda_fe_fd, FE_READ_SIGNAL_STRENGTH, &tdmi->tdmi_signal) < 0) + tdmi->tdmi_signal = 0; + + if(ioctl(tda->tda_fe_fd, FE_READ_SNR, &tdmi->tdmi_snr) < 0) + tdmi->tdmi_snr = 0; + + if(ioctl(tda->tda_fe_fd, FE_READ_BER, &tdmi->tdmi_ber) < 0) + tdmi->tdmi_ber = 0; + + if(ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, + &tdmi->tdmi_uncorrected_blocks) < 0) + tdmi->tdmi_uncorrected_blocks = 0; +} + + + + +static void * +dvb_monitor_thread(void *aux) +{ + th_dvb_adapter_t *tda = aux; + + while(1) { + sleep(1); + dvb_monitor_current_mux(tda); + } +} + + + + + +/* + * + */ +static int +dvb_service_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, + uint8_t tableid, void *opaque) +{ + th_transport_t *t = opaque; + + return dvb_parse_pmt(t, ptr, len); +} + + +/* * */ - -int -dvb_configure_transport(th_transport_t *t, const char *muxname) +th_transport_t * +dvb_find_transport(th_dvb_mux_instance_t *tdmi, uint16_t nid, uint16_t tid, + uint16_t sid, int create) { - struct dvb_frontend_parameters *fr_param; - config_entry_t *ce; - char buf[100]; - - if((ce = find_mux_config("dvbmux", muxname)) == NULL) - return -1; + th_transport_t *t; + th_dvb_mux_t *tdm = tdmi->tdmi_mux; + struct dmx_sct_filter_params fparams; + int fd; + LIST_FOREACH(t, &all_transports, tht_global_link) { + if(t->tht_dvb_network_id == nid && + t->tht_dvb_transport_id == tid && + t->tht_dvb_service_id == sid) + return t; + } + + if(!create) + return NULL; + + t = calloc(1, sizeof(th_transport_t)); + transport_monitor_init(t); + + t->tht_dvb_network_id = nid; + t->tht_dvb_transport_id = tid; + t->tht_dvb_service_id = sid; + + t->tht_dvb_mux = tdm; t->tht_type = TRANSPORT_DVB; - fr_param = &t->tht_dvb_fe_params; - - fr_param->inversion = INVERSION_AUTO; - fr_param->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; - fr_param->u.ofdm.code_rate_HP = FEC_2_3; - fr_param->u.ofdm.code_rate_LP = FEC_1_2; - fr_param->u.ofdm.constellation = QAM_64; - fr_param->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; - fr_param->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; - fr_param->u.ofdm.hierarchy_information = HIERARCHY_NONE; - fr_param->frequency = - atoi(config_get_str_sub(&ce->ce_sub, "frequency", "0")); + fd = open(tdmi->tdmi_adapter->tda_demux_path, O_RDWR); + if(fd == -1) { + free(t); + return NULL; + } - snprintf(buf, sizeof(buf), "DVB-T: %s (%.1f MHz)", muxname, - (float)fr_param->frequency / 1000000.0f); - t->tht_name = strdup(buf); + memset(&fparams, 0, sizeof(fparams)); + fparams.pid = sid; + fparams.timeout = 0; + fparams.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC; + fparams.filter.filter[0] = 0x02; + fparams.filter.mask[0] = 0xff; - return 0; + if(ioctl(fd, DMX_SET_FILTER, &fparams) < 0) { + close(fd); + free(t); + return NULL; + } + + tdt_add(tdmi, fd, dvb_service_callback, t, 0); + t->tht_name = strdup(tdm->tdm_title); + LIST_INSERT_HEAD(&all_transports, t, tht_global_link); + return t; } + + + + /* * DVB time and date functions */ @@ -383,30 +538,30 @@ convert_date(uint8_t *dvb_buf) } -static void -dvb_datetime_callback(int events, void *opaque, int fd) +static int +dvb_tdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, + uint8_t tableid, void *opaque) { - th_dvb_adapter_t *tda = opaque; - uint8_t sec[4096]; - int seclen, r; time_t t; + t = convert_date(buf); + tdmi->tdmi_time = t; + time(&t); + + t = tdmi->tdmi_time - t; - if(!(events & DISPATCH_READ)) - return; - - r = read(fd, sec, sizeof(sec)); - - seclen = ((sec[1] & 0x0f) << 8) | (sec[2] & 0xff); - if(r == seclen + 3) { - t = convert_date(&sec[3]); - tda->tda_time = t; + if(abs(t) > 5) { + syslog(LOG_NOTICE, + "\"%s\" DVB network clock is %lds off from system clock", + tdmi->tdmi_mux->tdm_name, t); } + return 0; } static void -dvb_datetime_add_demux(th_dvb_adapter_t *tda) +dvb_tdt_add_demux(th_dvb_mux_instance_t *tdmi) { + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; struct dmx_sct_filter_params fparams; int fd; @@ -425,6 +580,423 @@ dvb_datetime_add_demux(th_dvb_adapter_t *tda) close(fd); return; } - - dispatch_addfd(fd, dvb_datetime_callback, tda, DISPATCH_READ); + tdt_add(tdmi, fd, dvb_tdt_callback, NULL, 1); +} + + + + + +/* + * DVB Descriptor; Short Event + */ + +static int +dvb_desc_short_event(uint8_t *ptr, int len, + char *title, size_t titlelen, + char *desc, size_t desclen) +{ + int r; + + if(len < 4) + return -1; + ptr += 3; len -= 3; + + if((r = dvb_get_string_with_len(title, titlelen, ptr, len, "UTF8")) < 0) + return -1; + ptr += r; len -= r; + + if((r = dvb_get_string_with_len(desc, desclen, ptr, len, "UTF8")) < 0) + return -1; + + return 0; +} + + +/* + * DVB Descriptor; Service + */ + + + +static int +dvb_desc_service(uint8_t *ptr, int len, uint8_t *typep, + char *provider, size_t providerlen, + char *name, size_t namelen) +{ + int r; + + if(len < 2) + return -1; + + *typep = ptr[0]; + + ptr++; + len--; + + if((r = dvb_get_string_with_len(provider, providerlen, ptr, len, + "UTF8")) < 0) + return -1; + ptr += r; len -= r; + + if((r = dvb_get_string_with_len(name, namelen, ptr, len, + "UTF8")) < 0) + return -1; + ptr += r; len -= r; + return 0; +} + + + + + + + +/* + * DVB EIT (Event Information Table) + */ + + +static int +dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, + uint8_t tableid, void *opaque) +{ + th_transport_t *t; + th_channel_t *ch; + + uint16_t serviceid; + int version; + int current_next_indicator; + uint8_t section_number; + uint8_t last_section_number; + uint16_t transport_stream_id; + uint16_t original_network_id; + uint8_t segment_last_section_number; + uint8_t last_table_id; + + uint16_t event_id; + time_t start_time; + + int duration; + int dllen; + uint8_t dtag, dlen; + + char title[256]; + char desc[5000]; + + if(tableid < 0x4e || tableid > 0x6f) + return -1; + + if(len < 11) + return -1; + + serviceid = ptr[0] << 8 | ptr[1]; + version = ptr[2] >> 1 & 0x1f; + current_next_indicator = ptr[2] & 1; + section_number = ptr[3]; + last_section_number = ptr[4]; + transport_stream_id = ptr[5] << 8 | ptr[6]; + original_network_id = ptr[7] << 8 | ptr[8]; + segment_last_section_number = ptr[9]; + last_table_id = ptr[10]; + + len -= 11; + ptr += 11; + + t = dvb_find_transport(tdmi, original_network_id, transport_stream_id, + serviceid, 0); + if(t == NULL) + return -1; + ch = t->tht_channel; + if(ch == NULL) + return -1; + + epg_lock(); + + while(len >= 12) { + event_id = ptr[0] << 8 | ptr[1]; + start_time = convert_date(&ptr[2]); + duration = bcdtoint(ptr[7] & 0xff) * 3600 + + bcdtoint(ptr[8] & 0xff) * 60 + + bcdtoint(ptr[9] & 0xff); + dllen = ((ptr[10] & 0x0f) << 8) | ptr[11]; + + len -= 12; + ptr += 12; + + + + if(dllen > len) + break; + + while(dllen > 0) { + dtag = ptr[0]; + dlen = ptr[1]; + + len -= 2; ptr += 2; dllen -= 2; + + if(dlen > len) + break; + + switch(dtag) { + case DVB_DESC_SHORT_EVENT: + if(dvb_desc_short_event(ptr, dlen, + title, sizeof(title), + desc, sizeof(desc)) < 0) + duration = 0; + break; + } + + len -= dlen; ptr += dlen; dllen -= dlen; + } + + if(duration > 0) { + epg_update_event_by_id(ch, event_id, start_time, duration, + title, desc); + + } + } + + epg_unlock(); + return 0; +} + + + +static void +dvb_eit_add_demux(th_dvb_mux_instance_t *tdmi) +{ + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + struct dmx_sct_filter_params fparams; + int fd; + + fd = open(tda->tda_demux_path, O_RDWR); + if(fd == -1) + return; + + memset(&fparams, 0, sizeof(fparams)); + fparams.pid = 0x12; + fparams.timeout = 0; + fparams.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC; + + if(ioctl(fd, DMX_SET_FILTER, &fparams) < 0) { + close(fd); + return; + } + + tdt_add(tdmi, fd, dvb_eit_callback, NULL, 1); +} + + + + + +/* + * DVB SDT (Service Description Table) + */ + + +static int +dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, + uint8_t tableid, void *opaque) +{ + th_transport_t *t; + int version; + int current_next_indicator; + uint8_t section_number; + uint8_t last_section_number; + uint16_t service_id; + uint16_t transport_stream_id; + uint16_t original_network_id; + + int reserved; + int running_status, free_ca_mode; + int dllen; + uint8_t dtag, dlen; + + char provider[256]; + char chname[256]; + uint8_t stype; + int ret = 0; + + if(len < 8) + return -1; + + transport_stream_id = ptr[0] << 8 | ptr[1]; + version = ptr[2] >> 1 & 0x1f; + current_next_indicator = ptr[2] & 1; + section_number = ptr[3]; + last_section_number = ptr[4]; + original_network_id = ptr[5] << 8 | ptr[6]; + reserved = ptr[7]; + + len -= 8; + ptr += 8; + + + while(len >= 5) { + service_id = ptr[0] << 8 | ptr[1]; + reserved = ptr[2]; + running_status = (ptr[3] >> 5) & 0x7; + free_ca_mode = (ptr[3] >> 4) & 0x1; + dllen = ((ptr[3] & 0x0f) << 8) | ptr[4]; + + len -= 5; + ptr += 5; + + if(dllen > len) + break; + + stype = 0; + + while(dllen > 2) { + dtag = ptr[0]; + dlen = ptr[1]; + + len -= 2; ptr += 2; dllen -= 2; + + if(dlen > len) + break; + + switch(dtag) { + case DVB_DESC_SERVICE: + if(dvb_desc_service(ptr, dlen, &stype, + provider, sizeof(provider), + chname, sizeof(chname)) < 0) + stype = 0; + break; + } + + len -= dlen; ptr += dlen; dllen -= dlen; + } + + if(stype == 1 && + free_ca_mode == 0 /* We dont have any CA-support (yet) */) { + + t = dvb_find_transport(tdmi, original_network_id, + transport_stream_id, + service_id, 1); + + ret |= transport_set_channel(t, chname); + } + } + return ret; +} + + + +static void +dvb_sdt_add_demux(th_dvb_mux_instance_t *tdmi) +{ + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + struct dmx_sct_filter_params fparams; + int fd; + + fd = open(tda->tda_demux_path, O_RDWR); + if(fd == -1) + return; + + memset(&fparams, 0, sizeof(fparams)); + fparams.pid = 0x11; + fparams.timeout = 0; + fparams.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC; + fparams.filter.filter[0] = 0x42; + fparams.filter.mask[0] = 0xff; + + if(ioctl(fd, DMX_SET_FILTER, &fparams) < 0) { + close(fd); + return; + } + + tdt_add(tdmi, fd, dvb_sdt_callback, NULL, 0); +} + + +static void +tdmi_activate(th_dvb_mux_instance_t *tdmi) +{ + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + + if(tdmi->tdmi_initial_scan_timer != NULL) { + stimer_del(tdmi->tdmi_initial_scan_timer); + tdmi->tdmi_initial_scan_timer = NULL; + } + + tdmi->tdmi_state = TDMI_ACTIVE; + + LIST_REMOVE(tdmi, tdmi_adapter_link); + LIST_INSERT_HEAD(&tda->tda_muxes_active, tdmi, tdmi_adapter_link); + + /* tune to next configured (but not yet active) mux */ + + tdmi = LIST_FIRST(&tda->tda_muxes_configured); + + if(tdmi == NULL) { + syslog(LOG_INFO, + "\"%s\" Initial scan completed, adapter available", + tda->tda_name); + /* no more muxes to probe, link adapter to the world */ + LIST_REMOVE(tda, tda_link); + LIST_INSERT_HEAD(&dvb_adapters_running, tda, tda_link); + return; + } + + dvb_tune_tdmi(tdmi, 1); +} + + +static void +tdmi_initial_scan_timeout(void *aux) +{ + th_dvb_mux_instance_t *tdmi = aux; + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + const char *err; + + tdmi->tdmi_initial_scan_timer = NULL; + + err = "Unknown error"; + + if(tdmi->tdmi_status != NULL) + err = tdmi->tdmi_status; + + syslog(LOG_DEBUG, "\"%s\" on \"%s\" Initial scan timed out -- %s", + tdmi->tdmi_mux->tdm_name, tda->tda_name, err); + + tdmi_activate(tdmi); +} + + + +static void +tdmi_check_scan_status(th_dvb_mux_instance_t *tdmi) +{ + th_dvb_table_t *tdt; + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + + if(tdmi->tdmi_state == TDMI_ACTIVE) + return; + + LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link) + if(tdt->tdt_count == 0) + return; + + /* All tables seen at least once */ + + syslog(LOG_DEBUG, "\"%s\" on \"%s\" Initial scan completed", + tdmi->tdmi_mux->tdm_name, tda->tda_name); + + tdmi_activate(tdmi); +} + + + +static void +dvb_start_initial_scan(th_dvb_mux_instance_t *tdmi) +{ + dvb_tune_tdmi(tdmi, 1); + + tdmi->tdmi_state = TDMI_INITIAL_SCAN; + tdmi->tdmi_initial_scan_timer = + stimer_add(tdmi_initial_scan_timeout, tdmi, 5); + } diff --git a/input_dvb.h b/input_dvb.h index 9ac9a7ad..f5fdc470 100644 --- a/input_dvb.h +++ b/input_dvb.h @@ -19,15 +19,14 @@ #ifndef INPUT_DVB_H #define INPUT_DVB_H -void dvb_add_adapters(void); +extern struct th_dvb_adapter_list dvb_adapters_probing; +extern struct th_dvb_adapter_list dvb_adapters_running; +extern struct th_dvb_mux_list dvb_muxes; -th_dvb_adapter_t *dvb_alloc_adapter(struct dvb_frontend_parameters *params, - int force); +void dvb_init(void); int dvb_configure_transport(th_transport_t *t, const char *muxname); -int dvb_start_feed(th_transport_t *t, unsigned int weight); - -void dvb_stop_feed(th_transport_t *t); +int dvb_tune(th_dvb_adapter_t *tda, th_dvb_mux_t *tdm, int maylog); #endif /* INPUT_DVB_H */ diff --git a/main.c b/main.c index 4b548bc4..f3e45404 100644 --- a/main.c +++ b/main.c @@ -19,12 +19,13 @@ #include #include #include - +#include #include #include #include #include #include +#include #include #include @@ -40,16 +41,34 @@ #include "input_v4l.h" #include "channels.h" #include "output_client.h" +#include "epg.h" #include "epg_xmltv.h" #include "pvr.h" #include "dispatch.h" #include "output_multicast.h" -int reftally; int running; - int xmltvreload; +static pthread_mutex_t tag_mutex = PTHREAD_MUTEX_INITIALIZER; +static uint32_t tag_tally; + +uint32_t +tag_get(void) +{ + uint32_t r; + + pthread_mutex_lock(&tag_mutex); + r = ++tag_tally; + if(r == 0) + r = ++tag_tally; + + pthread_mutex_unlock(&tag_mutex); + return r; +} + + + static void xmltvdoreload(int x) { @@ -145,15 +164,16 @@ main(int argc, char **argv) signal(SIGINT, doexit); av_register_all(); - av_log_set_level(AV_LOG_QUIET); + av_log_set_level(AV_LOG_DEBUG); client_start(); - dvb_add_adapters(); + dvb_init(); // v4l_add_adapters(); channels_load(); - + + epg_init(); xmltv_init(); pvr_init(); @@ -196,3 +216,66 @@ find_mux_config(const char *muxtype, const char *muxname) } + +static char * +convert_to(const char *in, const char *target_encoding) +{ + iconv_t ic; + size_t inlen, outlen, alloced, pos; + char *out, *start; + int r; + + inlen = strlen(in); + alloced = 100; + outlen = alloced; + + ic = iconv_open(target_encoding, "UTF8"); + if(ic == NULL) + return NULL; + + out = start = malloc(alloced + 1); + + while(inlen > 0) { + r = iconv(ic, (char **)&in, &inlen, &out, &outlen); + + if(r == (size_t) -1) { + if(errno == EILSEQ) { + in++; + inlen--; + continue; + } + + if(errno == E2BIG) { + pos = alloced - outlen; + alloced *= 2; + start = realloc(start, alloced + 1); + out = start + pos; + outlen = alloced - pos; + continue; + } + break; + } + } + + iconv_close(ic); + pos = alloced - outlen; + start[pos] = 0; + return start; +} + + + + + +char * +utf8toprintable(const char *in) +{ + return convert_to(in, "ISO_8859-1"); +} + +char * +utf8tofilename(const char *in) +{ + return convert_to(in, "LATIN1"); +} + diff --git a/output_client.c b/output_client.c index 46ab233b..d2667957 100644 --- a/output_client.c +++ b/output_client.c @@ -54,8 +54,7 @@ cprintf(client_t *c, const char *fmt, ...) static void -client_ip_streamer(struct th_subscription *s, uint8_t *pkt, pidinfo_t *pi, - int streamindex) +client_ip_streamer(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi) { client_t *c = s->ths_opaque; char stoppkt[4]; @@ -138,8 +137,8 @@ cr_show(client_t *c, char **argv, int argc) th_subscription_t *s; th_transport_t *t; th_channel_t *ch; - programme_t *p; - int chid = 0; + event_t *e; + char *tmp; char *txt; int v, remain; @@ -164,20 +163,42 @@ cr_show(client_t *c, char **argv, int argc) if(!strcasecmp(subcmd, "channel")) { - while((ch = channel_by_id(chid++)) != NULL) { + + TAILQ_FOREACH(ch, &channels, ch_global_link) { - pthread_mutex_lock(&ch->ch_epg_mutex); - - p = epg_get_cur_prog(ch); + tmp = utf8toprintable(ch->ch_name); + cprintf(c, "%3d: \"%s\"\n", ch->ch_index, tmp); + free(tmp); - txt = p && p->pr_title ? p->pr_title: ""; - remain = p ? p->pr_stop - time(NULL) : 0; - remain /= 60; + epg_lock(); - cprintf(c, "%3d: \"%s\": \"%s\" %d:%02d remaining\n", - ch->ch_index, ch->ch_name, txt, remain / 60, remain % 60); + e = epg_event_get_current(ch); - pthread_mutex_unlock(&ch->ch_epg_mutex); + if(e != NULL) { + + tmp = utf8toprintable(e->e_title ?: ""); + + remain = e->e_start + e->e_duration - time(NULL); + remain /= 60; + + switch(e->e_source) { + case EVENT_SRC_XMLTV: + txt = "xmltv"; + break; + case EVENT_SRC_DVB: + txt = "dvb"; + break; + default: + txt = "???"; + break; + } + + cprintf(c, "\tNow: %-40s %2d:%02d [%s] tag: %d\n", + tmp, remain / 60, remain % 60, txt, e->e_tag); + free(tmp); + } + + epg_unlock(); LIST_FOREACH(t, &ch->ch_transports, tht_channel_link) { @@ -232,17 +253,6 @@ cr_show(client_t *c, char **argv, int argc) -/* - * - */ - -static int -cr_channels_total(client_t *c, char **argv, int argc) -{ - cprintf(c, "%d\n", channel_get_channels()); - return 0; -} - /* * */ @@ -252,10 +262,10 @@ cr_channel_reftag(client_t *c, char **argv, int argc) { th_channel_t *ch; - if(argc != 1 || (ch = channel_by_id(atoi(argv[0]))) == NULL) + if(argc != 1 || (ch = channel_by_index(atoi(argv[0]))) == NULL) return 1; - cprintf(c, "%d\n", ch->ch_ref); + cprintf(c, "%u\n", ch->ch_tag); return 0; } @@ -271,16 +281,16 @@ cr_channel_info(client_t *c, char **argv, int argc) if(argc < 1) return 1; - if((ch = channel_by_id(atoi(argv[0]))) == NULL) + if((ch = channel_by_index(atoi(argv[0]))) == NULL) return 1; cprintf(c, "displayname = %s\n" "icon = %s\n" - "reftag = %d\n", + "tag = %d\n", ch->ch_name, ch->ch_icon ?: "", - ch->ch_ref); + ch->ch_tag); return 0; } @@ -339,7 +349,7 @@ cr_channel_subscribe(client_t *c, char **argv, int argc) } } - if((ch = channel_by_id(chindex)) == NULL) + if((ch = channel_by_index(chindex)) == NULL) return 1; s = channel_subscribe(ch, c, client_ip_streamer, weight, "client"); @@ -377,44 +387,53 @@ cr_streamport(client_t *c, char **argv, int argc) */ static int -cr_programme_info(client_t *c, char **argv, int argc) +cr_event_info(client_t *c, char **argv, int argc) { - th_channel_t *ch; - programme_t *pr; + event_t *e, *x; + uint32_t tag, prev, next; if(argc < 1) return 1; - if((ch = channel_by_id(atoi(argv[0]))) == NULL) - return 1; + epg_lock(); - pthread_mutex_lock(&ch->ch_epg_mutex); + e = epg_event_find_by_tag(atoi(argv[0])); - pr = argc == 2 ? epg_get_prog_by_id(ch, atoi(argv[1])) : - epg_get_cur_prog(ch); - - if(pr == NULL) { - pthread_mutex_unlock(&ch->ch_epg_mutex); + if(e == NULL) { + epg_unlock(); return 1; } + tag = e->e_tag; + x = TAILQ_PREV(e, event_queue, e_link); + prev = x != NULL ? x->e_tag : 0; + + x = TAILQ_NEXT(e, e_link); + next = x != NULL ? x->e_tag : 0; + cprintf(c, - "index = %d\n" - "title = %s\n" "start = %ld\n" "stop = %ld\n" + "title = %s\n" "desc = %s\n" - "reftag = %d\n" + "tag = %u\n" + "prev = %u\n" + "next = %u\n" "pvrstatus = %c\n", - pr->pr_index, - pr->pr_title ?: "", - pr->pr_start, - pr->pr_stop, - pr->pr_desc ?: "", - pr->pr_ref, - pvr_prog_status(pr)); - pthread_mutex_unlock(&ch->ch_epg_mutex); + e->e_start, + e->e_start + e->e_duration, + + e->e_title ?: "", + e->e_desc ?: "", + + tag, + prev, + next, + + pvr_prog_status(e)); + + epg_unlock(); return 0; } @@ -423,30 +442,28 @@ cr_programme_info(client_t *c, char **argv, int argc) */ static int -cr_programme_bytime(client_t *c, char **argv, int argc) +cr_event_bytime(client_t *c, char **argv, int argc) { th_channel_t *ch; - programme_t *pr; - time_t t; + event_t *e; if(argc < 2) return 1; - if((ch = channel_by_id(atoi(argv[0]))) == NULL) + if((ch = channel_by_index(atoi(argv[0]))) == NULL) return 1; - t = atoi(argv[1]); + epg_lock(); - pthread_mutex_lock(&ch->ch_epg_mutex); + e = epg_event_find_by_time(ch, atoi(argv[1])); - pr = epg_find_programme_by_time(ch, t); - if(pr == NULL) { - pthread_mutex_unlock(&ch->ch_epg_mutex); + if(e == NULL) { + epg_unlock(); return 1; } - cprintf(c, "%d\n", pr->pr_index); - pthread_mutex_unlock(&ch->ch_epg_mutex); + cprintf(c, "%u\n", e->e_tag); + epg_unlock(); return 0; } @@ -455,30 +472,24 @@ cr_programme_bytime(client_t *c, char **argv, int argc) */ static int -cr_programme_record(client_t *c, char **argv, int argc) +cr_event_record(client_t *c, char **argv, int argc) { - th_channel_t *ch; - programme_t *pr; + event_t *e; if(argc < 1) return 1; - if((ch = channel_by_id(atoi(argv[0]))) == NULL) - return 1; + epg_lock(); - pthread_mutex_lock(&ch->ch_epg_mutex); - - pr = argc == 2 ? epg_get_prog_by_id(ch, atoi(argv[1])) : - epg_get_cur_prog(ch); - - - if(pr == NULL) { - pthread_mutex_unlock(&ch->ch_epg_mutex); + e = epg_event_find_by_tag(atoi(argv[0])); + if(e == NULL) { + epg_unlock(); return 1; } - pvr_add_recording_by_pr(ch, pr); - pthread_mutex_unlock(&ch->ch_epg_mutex); + pvr_add_recording_by_event(e->e_ch, e); + + epg_unlock(); return 0; } @@ -488,7 +499,7 @@ cr_programme_record(client_t *c, char **argv, int argc) static int cr_pvr_entry(client_t *c, pvr_rec_t *pvrr) { - programme_t *pr; + event_t *e; if(pvrr == NULL) return 1; @@ -498,7 +509,7 @@ cr_pvr_entry(client_t *c, pvr_rec_t *pvrr) "start = %ld\n" "stop = %ld\n" "desc = %s\n" - "reftag = %d\n" + "tag = %d\n" "pvrstatus = %c\n" "filename = %s\n" "channel = %d\n", @@ -512,10 +523,9 @@ cr_pvr_entry(client_t *c, pvr_rec_t *pvrr) pvrr->pvrr_channel->ch_index); - pr = epg_find_programme(pvrr->pvrr_channel, pvrr->pvrr_start, - pvrr->pvrr_stop); - if(pr != NULL) - cprintf(c, "index = %d\n", pr->pr_index); + e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start); + if(e != NULL) + cprintf(c, "event_tag = %d\n", e->e_tag); return 0; } @@ -564,14 +574,13 @@ const struct { } cr_cmds[] = { { "show", cr_show }, { "streamport", cr_streamport }, - { "channels.total", cr_channels_total }, { "channel.reftag", cr_channel_reftag }, { "channel.info", cr_channel_info }, { "channel.subscribe", cr_channel_subscribe }, { "channel.unsubscribe", cr_channel_unsubscribe }, - { "programme.info", cr_programme_info }, - { "programme.bytime", cr_programme_bytime }, - { "programme.record", cr_programme_record }, + { "event.info", cr_event_info }, + { "event.bytime", cr_event_bytime }, + { "event.record", cr_event_record }, { "pvr.getlog", cr_pvr_getlog }, { "pvr.gettag", cr_pvr_gettag }, }; @@ -847,7 +856,7 @@ client_status_update(void) th_channel_t *ch; th_dvb_adapter_t *dvb; th_v4l_adapter_t *v4l; - const char *info; + // const char *info; th_subscription_t *s; th_transport_t *t; int ccerr; @@ -882,7 +891,7 @@ client_status_update(void) continue; } - +#if 0 if(dvb->tda_fe_status & FE_HAS_LOCK) { csprintf(c, ch, "status = 1\n" @@ -912,7 +921,7 @@ client_status_update(void) info = "No lock, but faint signal"; else info = "No signal"; - + csprintf(c, ch, "status = 0" "info = %s" @@ -921,6 +930,7 @@ client_status_update(void) info, dvb->tda_name, t->tht_name); +#endif break; diff --git a/output_multicast.c b/output_multicast.c index 71b548d1..f0b77414 100644 --- a/output_multicast.c +++ b/output_multicast.c @@ -44,8 +44,7 @@ typedef struct output_multicast { static void -om_ip_streamer(struct th_subscription *s, uint8_t *pkt, pidinfo_t *pi, - int streamindex) +om_ip_streamer(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi) { output_multicast_t *om = s->ths_opaque; struct sockaddr_in sin; diff --git a/pvr.c b/pvr.c index 58e3cc2a..e5dbfcb0 100644 --- a/pvr.c +++ b/pvr.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -72,14 +71,14 @@ pvr_init(void) } char -pvr_prog_status(programme_t *pr) +pvr_prog_status(event_t *e) { pvr_rec_t *pvrr; LIST_FOREACH(pvrr, &pvrr_global_list, pvrr_global_link) { - if(pvrr->pvrr_start >= pr->pr_start && - pvrr->pvrr_stop <= pr->pr_stop && - pvrr->pvrr_channel == pr->pr_ch) { + if(pvrr->pvrr_start >= e->e_start && + pvrr->pvrr_stop <= e->e_start + e->e_duration && + pvrr->pvrr_channel == e->e_ch) { return pvrr->pvrr_status; } } @@ -169,15 +168,14 @@ pvr_main_thread(void *aux) void pvr_inform_status_change(pvr_rec_t *pvrr) { - programme_t *pr; + event_t *e; clients_enq_ref(pvrr->pvrr_ref); - pr = epg_find_programme(pvrr->pvrr_channel, pvrr->pvrr_start, - pvrr->pvrr_stop); - - if(pr != NULL) - clients_enq_ref(pr->pr_ref); + e = epg_event_find_by_time(pvrr->pvrr_channel, pvrr->pvrr_start); + + if(e != NULL) + clients_enq_ref(e->e_tag); } @@ -250,7 +248,7 @@ pvr_link_pvrr(pvr_rec_t *pvrr) break; } - pvrr->pvrr_ref = ++reftally; + pvrr->pvrr_ref = tag_get(); LIST_INSERT_SORTED(&pvrr_global_list, pvrr, pvrr_global_link, pvr_glob_cmp); LIST_INSERT_SORTED(l, pvrr, pvrr_work_link, pvr_rec_cmp); @@ -260,10 +258,10 @@ pvr_link_pvrr(pvr_rec_t *pvrr) void -pvr_add_recording_by_pr(th_channel_t *ch, programme_t *pr) +pvr_add_recording_by_event(th_channel_t *ch, event_t *e) { - time_t start = pr->pr_start; - time_t stop = pr->pr_stop; + time_t start = e->e_start; + time_t stop = e->e_start + e->e_duration; time_t now; pvr_rec_t *pvrr; @@ -287,8 +285,8 @@ pvr_add_recording_by_pr(th_channel_t *ch, programme_t *pr) pvrr->pvrr_channel = ch; pvrr->pvrr_start = start; pvrr->pvrr_stop = stop; - pvrr->pvrr_title = pr->pr_title ? strdup(pr->pr_title) : NULL; - pvrr->pvrr_desc = pr->pr_desc ? strdup(pr->pr_desc) : NULL; + pvrr->pvrr_title = e->e_title ? strdup(e->e_title) : NULL; + pvrr->pvrr_desc = e->e_desc ? strdup(e->e_desc) : NULL; pvr_link_pvrr(pvrr); pvr_database_save(); diff --git a/pvr.h b/pvr.h index 59abe5bf..4ea7c838 100644 --- a/pvr.h +++ b/pvr.h @@ -26,9 +26,9 @@ extern struct pvr_rec_list pvrr_global_list; void pvr_init(void); -void pvr_add_recording_by_pr(th_channel_t *ch, programme_t *pr); +void pvr_add_recording_by_event(th_channel_t *ch, event_t *e); -char pvr_prog_status(programme_t *pr); +char pvr_prog_status(event_t *e); pvr_rec_t *pvr_get_log_entry(int e); diff --git a/pvr_rec.c b/pvr_rec.c index 4a59fb47..3586924f 100644 --- a/pvr_rec.c +++ b/pvr_rec.c @@ -81,7 +81,7 @@ static int pwo_end(pvr_rec_t *pvrr); static void pvr_generate_filename(pvr_rec_t *pvrr); static void pvr_record_callback(struct th_subscription *s, uint8_t *pkt, - pidinfo_t *pi, int streamindex); + th_pid_t *pi); @@ -290,8 +290,7 @@ pvr_recorder_thread(void *aux) */ static void -pvr_record_callback(struct th_subscription *s, uint8_t *pkt, pidinfo_t *pi, - int streamindex) +pvr_record_callback(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi) { pvr_data_t *pd; pvr_rec_t *pvrr = s->ths_opaque; @@ -301,7 +300,6 @@ pvr_record_callback(struct th_subscription *s, uint8_t *pkt, pidinfo_t *pi, pd = malloc(sizeof(pvr_data_t)); pd->tsb = malloc(188); - pd->streamindex = streamindex; memcpy(pd->tsb, pkt, 188); pd->pi = *pi; pthread_mutex_lock(&pvrr->pvrr_dq_mutex); @@ -333,15 +331,16 @@ deslashify(char *s) static void pvr_generate_filename(pvr_rec_t *pvrr) { - char tmp1[200]; - char fullname[500]; - char *x, *chname, *out = tmp1; - size_t ibl, tmp1len = sizeof(tmp1) - 1; + char fullname[1000]; + char *x; int tally = 0; struct stat st; - iconv_t ic; char *name = pvrr->pvrr_title; + char *chname; + char *filename; + + if(pvrr->pvrr_filename != NULL) { free(pvrr->pvrr_filename); pvrr->pvrr_filename = NULL; @@ -350,35 +349,14 @@ pvr_generate_filename(pvr_rec_t *pvrr) free(pvrr->pvrr_format); pvrr->pvrr_format = strdup("asf"); - if(name != NULL && name[0] != 0) { - /* Convert from utf8 */ + filename = utf8tofilename(name && name[0] ? name : "untitled"); + deslashify(filename); - ic = iconv_open("ISO_8859-1", "UTF8"); - if(ic != (iconv_t) -1) { - - memset(tmp1, 0, sizeof(tmp1)); - - ibl = strlen(pvrr->pvrr_title); - iconv(ic, &name, &ibl, &out, &tmp1len); - iconv_close(ic); - out = tmp1; - } else { - out = name; - } - - deslashify(out); - - } else { - strcpy(tmp1, "untitled"); - out = tmp1; - } - - chname = alloca(strlen(pvrr->pvrr_channel->ch_name) + 1); - strcpy(chname, pvrr->pvrr_channel->ch_name); + chname = utf8tofilename(pvrr->pvrr_channel->ch_name); deslashify(chname); snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s", - config_get_str("pvrdir", "."), chname, out, pvrr->pvrr_format); + config_get_str("pvrdir", "."), chname, filename, pvrr->pvrr_format); while(1) { if(stat(fullname, &st) == -1) { @@ -392,7 +370,7 @@ pvr_generate_filename(pvr_rec_t *pvrr) tally++; snprintf(fullname, sizeof(fullname), "%s/%s-%s-%d.%s", - config_get_str("pvrdir", "."), chname, out, tally, + config_get_str("pvrdir", "."), chname, filename, tally, pvrr->pvrr_format); } @@ -404,6 +382,9 @@ pvr_generate_filename(pvr_rec_t *pvrr) x = strrchr(pvrr->pvrr_filename, '/'); pvrr->pvrr_printname = strdup(x ? x + 1 : pvrr->pvrr_filename); + + free(filename); + free(chname); } @@ -496,7 +477,7 @@ pvr_proc_tsb(pvr_rec_t *pvrr, struct ts_pid_head *pidlist, pvr_data_t *pd, sc = getu32(b, l); getu16(b, l); /* Skip len */ - if(pwo_writepkt(pvrr, s, sc, pd->streamindex, b, l, pd->pi.type)) + if(pwo_writepkt(pvrr, s, sc, pd->pi.tp_index, b, l, pd->pi.tp_type)) return 1; } tsp->tsp_bufptr = 0; @@ -564,7 +545,7 @@ pwo_init(th_subscription_t *s, pvr_rec_t *pvrr) int i, err; th_transport_t *t = s->ths_transport; pwo_ffmpeg_t *pf; - pidinfo_t *p; + th_pid_t *p; AVStream *st; AVCodec *codec; const char *cname; @@ -607,10 +588,10 @@ pwo_init(th_subscription_t *s, pvr_rec_t *pvrr) av_set_parameters(pf->fctx, NULL); /* Fix NULL -stuff */ - for(i = 0; i < t->tht_npids; i++) { - p = t->tht_pids + i; + LIST_FOREACH(p, &t->tht_pids, tp_link) { + i = p->tp_index; - switch(p->type) { + switch(p->tp_type) { case HTSTV_MPEG2VIDEO: break; case HTSTV_MPEG2AUDIO: @@ -629,7 +610,7 @@ pwo_init(th_subscription_t *s, pvr_rec_t *pvrr) st->codec = avcodec_alloc_context(); - switch(p->type) { + switch(p->tp_type) { default: continue; case HTSTV_MPEG2VIDEO: diff --git a/transports.c b/transports.c index c60760c0..a091cb7b 100644 --- a/transports.c +++ b/transports.c @@ -36,7 +36,7 @@ #include "tvhead.h" #include "dispatch.h" -#include "input_dvb.h" +#include "dvb_dvr.h" #include "input_v4l.h" #include "input_iptv.h" #include "teletext.h" @@ -48,6 +48,21 @@ static pthread_mutex_t subscription_mutex = PTHREAD_MUTEX_INITIALIZER; +/* + * + */ +void +subscription_lock(void) +{ + pthread_mutex_lock(&subscription_mutex); +} + +void +subscription_unlock(void) +{ + pthread_mutex_unlock(&subscription_mutex); +} + /* * @@ -186,7 +201,7 @@ subscription_unsubscribe(th_subscription_t *s) { pthread_mutex_lock(&subscription_mutex); - s->ths_callback(s, NULL, NULL, 0); + s->ths_callback(s, NULL, NULL); LIST_REMOVE(s, ths_global_link); LIST_REMOVE(s, ths_channel_link); @@ -221,8 +236,7 @@ subscription_sort(th_subscription_t *a, th_subscription_t *b) th_subscription_t * channel_subscribe(th_channel_t *ch, void *opaque, void (*callback)(struct th_subscription *s, - uint8_t *pkt, pidinfo_t *pi, - int streamindex), + uint8_t *pkt, th_pid_t *pi), unsigned int weight, const char *name) { @@ -289,7 +303,7 @@ transport_flush_subscribers(th_transport_t *t) while((s = LIST_FIRST(&t->tht_subscriptions)) != NULL) { LIST_REMOVE(s, ths_transport_link); s->ths_transport = NULL; - s->ths_callback(s, NULL, NULL, 0); + s->ths_callback(s, NULL, NULL); } } @@ -320,17 +334,14 @@ transport_compute_weight(struct th_transport_list *head) void transport_recv_tsb(th_transport_t *t, int pid, uint8_t *tsb) { - pidinfo_t *pi = NULL; + th_pid_t *pi = NULL; th_subscription_t *s; th_channel_t *ch; - int i, cc, err = 0; + int cc, err = 0; - for(i = 0; i < t->tht_npids; i++) { - if(t->tht_pids[i].pid == pid) { - pi = t->tht_pids + i; + LIST_FOREACH(pi, &t->tht_pids, tp_link) + if(pi->tp_pid == pid) break; - } - } if(pi == NULL) return; @@ -338,44 +349,45 @@ transport_recv_tsb(th_transport_t *t, int pid, uint8_t *tsb) cc = tsb[3] & 0xf; if(tsb[3] & 0x10) { - if(pi->cc_valid && cc != pi->cc) { + if(pi->tp_cc_valid && cc != pi->tp_cc) { /* Incorrect CC */ avgstat_add(&t->tht_cc_errors, 1, dispatch_clock); err = 1; } - pi->cc_valid = 1; - pi->cc = (cc + 1) & 0xf; + pi->tp_cc_valid = 1; + pi->tp_cc = (cc + 1) & 0xf; } avgstat_add(&t->tht_rate, 188, dispatch_clock); ch = t->tht_channel; - if(pi->type == HTSTV_TELETEXT) { + if(pi->tp_type == HTSTV_TELETEXT) { /* teletext */ teletext_input(t, tsb); return; } - tsb[0] = pi->type; + tsb[0] = pi->tp_type; pthread_mutex_lock(&subscription_mutex); LIST_FOREACH(s, &t->tht_subscriptions, ths_transport_link) { s->ths_total_err += err; - s->ths_callback(s, tsb, pi, i); + s->ths_callback(s, tsb, pi); } pthread_mutex_unlock(&subscription_mutex); } - static void transport_monitor(void *aux) - { +{ th_transport_t *t = aux; int v; + stimer_add(transport_monitor, t, 1); + if(t->tht_status == TRANSPORT_IDLE) return; @@ -405,5 +417,26 @@ transport_monitor_init(th_transport_t *t) avgstat_init(&t->tht_cc_errors, 3600); avgstat_init(&t->tht_rate, 10); - dispatch_add_1sec_event(transport_monitor, t); + stimer_add(transport_monitor, t, 5); +} + + +void +transport_add_pid(th_transport_t *t, uint16_t pid, tv_streamtype_t type) +{ + th_pid_t *pi; + int i = 0; + LIST_FOREACH(pi, &t->tht_pids, tp_link) { + i++; + if(pi->tp_pid == pid) + return; + } + + pi = calloc(1, sizeof(th_pid_t)); + pi->tp_index = i; + pi->tp_pid = pid; + pi->tp_type = type; + pi->tp_demuxer_fd = -1; + + LIST_INSERT_HEAD(&t->tht_pids, pi, tp_link); } diff --git a/transports.h b/transports.h index 80afe158..4918fb2d 100644 --- a/transports.h +++ b/transports.h @@ -23,6 +23,9 @@ void subscription_unsubscribe(th_subscription_t *s); void subscription_set_weight(th_subscription_t *s, unsigned int weight); +void subscription_lock(void); +void subscription_unlock(void); + unsigned int transport_compute_weight(struct th_transport_list *head); void transport_flush_subscribers(th_transport_t *t); @@ -31,11 +34,14 @@ void transport_recv_tsb(th_transport_t *t, int pid, uint8_t *tsb); void transport_monitor_init(th_transport_t *t); +void transport_add_pid(th_transport_t *t, uint16_t pid, tv_streamtype_t type); + +int transport_set_channel(th_transport_t *th, const char *name); + th_subscription_t *channel_subscribe(th_channel_t *ch, void *opaque, void (*ths_callback) (struct th_subscription *s, - uint8_t *pkt, pidinfo_t *pi, - int streamindex), + uint8_t *pkt, th_pid_t *pi), unsigned int weight, const char *name); diff --git a/tvhead.h b/tvhead.h index e893f1e4..090a628d 100644 --- a/tvhead.h +++ b/tvhead.h @@ -35,14 +35,19 @@ TAILQ_HEAD(th_channel_queue, th_channel); LIST_HEAD(th_dvb_adapter_list, th_dvb_adapter); LIST_HEAD(th_v4l_adapter_list, th_v4l_adapter); LIST_HEAD(client_list, client); -LIST_HEAD(programme_list, programme); +LIST_HEAD(event_list, event); +TAILQ_HEAD(event_queue, event); LIST_HEAD(pvr_rec_list, pvr_rec); TAILQ_HEAD(ref_update_queue, ref_update); LIST_HEAD(th_transport_list, th_transport); +LIST_HEAD(th_dvb_mux_list, th_dvb_mux); +LIST_HEAD(th_dvb_mux_instance_list, th_dvb_mux_instance); +LIST_HEAD(th_pid_list, th_pid); extern time_t dispatch_clock; +extern struct th_transport_list all_transports; -extern int reftally; +uint32_t tag_get(void); typedef struct th_v4l_adapter { @@ -64,36 +69,102 @@ typedef struct th_v4l_adapter { } th_v4l_adapter_t; +/* + * + */ + +typedef struct th_dvb_mux_instance { + LIST_ENTRY(th_dvb_mux_instance) tdmi_mux_link; + LIST_ENTRY(th_dvb_mux_instance) tdmi_adapter_link; + + struct th_dvb_mux *tdmi_mux; + struct th_dvb_adapter *tdmi_adapter; + + fe_status_t tdmi_fe_status; + uint16_t tdmi_snr, tdmi_signal; + uint32_t tdmi_ber, tdmi_uncorrected_blocks; + + time_t tdmi_time; + LIST_HEAD(, th_dvb_table) tdmi_tables; + + enum { + TDMI_CONFIGURED, + TDMI_INITIAL_SCAN, + TDMI_ACTIVE, + } tdmi_state; + + void *tdmi_initial_scan_timer; + const char *tdmi_status; + +} th_dvb_mux_instance_t; + +/* + * + */ + +typedef struct th_dvb_table { + LIST_ENTRY(th_dvb_table) tdt_link; + void *tdt_handle; + struct th_dvb_mux_instance *tdt_tdmi; + int tdt_count; /* times seen */ + void *tdt_opaque; + int (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, + uint8_t tableid, void *opaque); +} th_dvb_table_t; + + +/* + * + */ + +typedef struct th_dvb_mux { + LIST_ENTRY(th_dvb_mux) tdm_global_link; + + struct th_dvb_mux_instance_list tdm_instances; + + struct dvb_frontend_parameters tdm_fe_params; + + const char *tdm_name; + const char *tdm_title; + +} th_dvb_mux_t; + + typedef struct th_dvb_adapter { + LIST_ENTRY(th_dvb_adapter) tda_adapter_to_mux_link; + + struct th_dvb_mux_instance_list tda_muxes_configured; + + struct th_dvb_mux_instance_list tda_muxes_active; + th_dvb_mux_instance_t *tda_mux_current; + const char *tda_path; const char *tda_name; LIST_ENTRY(th_dvb_adapter) tda_link; + LIST_HEAD(, th_dvb_mux) tda_muxes; + int tda_running; - struct dvb_frontend_parameters tda_fe_params; - struct th_transport_list tda_transports; int tda_fe_fd; struct dvb_frontend_info tda_fe_info; - fe_status_t tda_fe_status; - uint16_t tda_snr, tda_signal; - uint32_t tda_ber, tda_uncorrected_blocks; - char *tda_demux_path; - int tda_dvr_fd; char *tda_dvr_path; - time_t tda_time; + struct th_transport_list tda_transports; /* Currently bound transports */ } th_dvb_adapter_t; + + + /* * * @@ -112,15 +183,17 @@ typedef struct th_iptv_adapter { -typedef struct pidinfo { - int pid; - tv_streamtype_t type; - int demuxer_fd; +typedef struct th_pid { + LIST_ENTRY(th_pid) tp_link; + uint16_t tp_pid; + uint8_t tp_cc; /* Last CC */ + uint8_t tp_cc_valid; /* Is CC valid at all? */ - int cc; /* Last CC */ - int cc_valid; /* Is CC valid at all? */ + tv_streamtype_t tp_type; + int tp_demuxer_fd; + int tp_index; -} pidinfo_t; +} th_pid_t; typedef enum { COMMERCIAL_UNKNOWN, @@ -133,6 +206,8 @@ typedef struct th_transport { const char *tht_name; + LIST_ENTRY(th_transport) tht_global_link; + enum { TRANSPORT_DVB, TRANSPORT_IPTV, @@ -150,11 +225,13 @@ typedef struct th_transport { int tht_tt_rundown_content_length; time_t tht_tt_clock; /* Network clock as determined by teletext decoder */ - int tht_prio; - pidinfo_t *tht_pids; - int tht_npids; + struct th_pid_list tht_pids; + + uint16_t tht_dvb_network_id; + uint16_t tht_dvb_transport_id; + uint16_t tht_dvb_service_id; avgstat_t tht_cc_errors; avgstat_t tht_rate; @@ -165,13 +242,12 @@ typedef struct th_transport { LIST_ENTRY(th_transport) tht_channel_link; struct th_channel *tht_channel; - LIST_HEAD(, th_subscription) tht_subscriptions; union { struct { - struct dvb_frontend_parameters fe_params; + struct th_dvb_mux *mux; struct th_dvb_adapter *adapter; } dvb; @@ -202,7 +278,7 @@ typedef struct th_transport { #define tht_v4l_frequency u.v4l.frequency #define tht_v4l_adapter u.v4l.adapter -#define tht_dvb_fe_params u.dvb.fe_params +#define tht_dvb_mux u.dvb.mux #define tht_dvb_adapter u.dvb.adapter #define tht_iptv_group_addr u.iptv.group_addr @@ -240,17 +316,6 @@ typedef struct tt_decoder { } tt_decoder_t; - -typedef struct th_proglist { - - struct programme_list tpl_programs; // linked list of all programs - unsigned int tpl_nprograms; // number of programs - struct programme **tpl_prog_vec; // array pointing to all programs - struct programme *tpl_prog_current; // pointer to current programme - const char *tpl_refname; // channel reference name - -} th_proglist_t; - /* * Channel definition */ @@ -272,27 +337,15 @@ typedef struct th_channel { struct tt_decoder ch_tt; - int ch_ref; + int ch_tag; int ch_teletext_rundown; - - - pthread_mutex_t ch_epg_mutex; ///< protects the epg fields - - th_proglist_t ch_xmltv; + struct event_queue ch_epg_events; + struct event *ch_epg_cur_event; } th_channel_t; -/* - * XXXX: DELETE ME? - */ - -typedef struct ref_update { - TAILQ_ENTRY(ref_update) link; - int ref; -} ref_update_t; - /* * Subscription */ @@ -313,8 +366,8 @@ typedef struct th_subscription { LIST_ENTRY(th_subscription) ths_subscriber_link; /* Caller is responsible for this link */ - void (*ths_callback)(struct th_subscription *s, uint8_t *pkt, pidinfo_t *pi, - int streamindex); + void (*ths_callback)(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi); + void *ths_opaque; char *ths_pkt; @@ -361,26 +414,31 @@ typedef struct client { /* - * EPG Programme + * EPG event */ -typedef struct programme { - LIST_ENTRY(programme) pr_link; +typedef struct event { + TAILQ_ENTRY(event) e_link; + LIST_ENTRY(event) e_hash_link; - time_t pr_start; - time_t pr_stop; + time_t e_start; /* UTC time */ + int e_duration; /* in seconds */ - char *pr_title; - char *pr_desc; + const char *e_title; /* UTF-8 encoded */ + const char *e_desc; /* UTF-8 encoded */ - th_channel_t *pr_ch; + uint16_t e_event_id; /* DVB event id */ - int pr_ref; - int pr_index; + uint32_t e_tag; - int pr_delete_me; + th_channel_t *e_ch; -} programme_t; + int e_source; /* higer is better, and we never downgrade */ + +#define EVENT_SRC_XMLTV 1 +#define EVENT_SRC_DVB 2 + +} event_t; /* @@ -391,8 +449,7 @@ typedef struct programme { typedef struct pvr_data { TAILQ_ENTRY(pvr_data) link; uint8_t *tsb; - pidinfo_t pi; - int streamindex; + th_pid_t pi; } pvr_data_t; @@ -439,4 +496,7 @@ extern struct pvr_rec_list pvrr_global_list; config_entry_t *find_mux_config(const char *muxtype, const char *muxname); +char *utf8toprintable(const char *in); +char *utf8tofilename(const char *in); + #endif /* TV_HEAD_H */