diff --git a/Makefile b/Makefile index 6f48482e..f604195d 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -include ../config.mak SRCS = main.c dispatch.c channels.c transports.c teletext.c psi.c \ - subscriptions.c ts.c + subscriptions.c tsmux.c tsdemux.c pes.c buffer.c -SRCS += pvr.c pvr_rec.c +SRCS += pvr.c SRCS += epg.c epg_xmltv.c diff --git a/buffer.c b/buffer.c new file mode 100644 index 00000000..d00cd7be --- /dev/null +++ b/buffer.c @@ -0,0 +1,362 @@ +/* + * Packet / Buffer management + * 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 . + */ + +#define _XOPEN_SOURCE 500 +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "tvhead.h" +#include "buffer.h" + + +int64_t store_mem_size; +int64_t store_mem_size_max; +int64_t store_disk_size; +int64_t store_disk_size_max; +int store_packets; + + + + +static struct th_pkt_queue store_mem_queue; +static struct th_pkt_queue store_disk_queue; + +static int store_chunk_size; +static const char *store_path; +static th_storage_t *curstore; +static int store_tally; + + +static void storage_wipe(void); +static void storage_mem_enq(th_pkt_t *pkt); +static void storage_disk_enq(th_pkt_t *pkt); + +static void storage_deref(th_storage_t *s); + +/* + * + */ +void +pkt_init(void) +{ + store_path = config_get_str("trickplay-path", "/storage/streambuffer"); + + if(store_path != NULL) + storage_wipe(); + + TAILQ_INIT(&store_mem_queue); + TAILQ_INIT(&store_disk_queue); + + store_mem_size_max = 1024 * 1024 * 10ULL; + store_disk_size_max = 1024 * 1024 * 4000ULL; + + store_chunk_size = store_disk_size_max / 32; +} + +/* + * + */ +static void +pkt_free(th_pkt_t *pkt) +{ + assert(pkt->pkt_storage == NULL); + free(pkt->pkt_payload); + memset(pkt, 0xff, sizeof(th_pkt_t)); + store_packets--; + free(pkt); +} + + +/* + * + */ +void +pkt_deref(th_pkt_t *pkt) +{ + assert(pkt->pkt_refcount > 0); + if(pkt->pkt_refcount > 1) { + pkt->pkt_refcount--; + return; + } + pkt_free(pkt); +} + +/* + * + */ +th_pkt_t * +pkt_ref(th_pkt_t *pkt) +{ + pkt->pkt_refcount++; + return pkt; +} + +/* + * + */ +th_pkt_t * +pkt_alloc(void *data, size_t datalen, int64_t pts, int64_t dts) +{ + th_pkt_t *pkt; + + pkt = calloc(1, sizeof(th_pkt_t)); + pkt->pkt_payloadlen = datalen; + pkt->pkt_payload = malloc(datalen); + if(data != NULL) + memcpy(pkt->pkt_payload, data, datalen); + + pkt->pkt_dts = dts; + pkt->pkt_pts = pts; + pkt->pkt_refcount = 1; + + store_packets++; + return pkt; +} + +/* + * + */ +th_pkt_t * +pkt_copy(th_pkt_t *orig) +{ + th_pkt_t *pkt; + + pkt_load(orig); + if(orig->pkt_payload == NULL) + return NULL; + + pkt = malloc(sizeof(th_pkt_t)); + memcpy(pkt, orig, sizeof(th_pkt_t)); + + pkt->pkt_payload = malloc(pkt->pkt_payloadlen); + memcpy(pkt->pkt_payload, orig->pkt_payload, pkt->pkt_payloadlen); + + pkt->pkt_on_stream_queue = 0; + pkt->pkt_storage = NULL; + pkt->pkt_refcount = 1; + return pkt; +} + + +/* + * + */ +void +pkt_store(th_pkt_t *pkt) +{ + th_stream_t *st = pkt->pkt_stream; + + if(pkt->pkt_on_stream_queue) + return; + + pkt->pkt_on_stream_queue = 1; + pkt->pkt_refcount++; + TAILQ_INSERT_TAIL(&st->st_pktq, pkt, pkt_queue_link); + + /* Persistent buffer management */ + + storage_mem_enq(pkt); + storage_disk_enq(pkt); + + pwrite(pkt->pkt_storage->ts_fd, pkt->pkt_payload, pkt->pkt_payloadlen, + pkt->pkt_storage_offset); +} + + +/* + * Force flush of a packet (if a transport is stopped) + */ +void +pkt_unstore(th_pkt_t *pkt) +{ + th_stream_t *st = pkt->pkt_stream; + + assert(pkt->pkt_on_stream_queue == 1); + TAILQ_REMOVE(&st->st_pktq, pkt, pkt_queue_link); + pkt->pkt_on_stream_queue = 0; + + if(pkt->pkt_storage != NULL) { + storage_deref(pkt->pkt_storage); + TAILQ_REMOVE(&store_disk_queue, pkt, pkt_disk_link); + store_disk_size -= pkt->pkt_payloadlen; + + if(pkt->pkt_payload != NULL) { + TAILQ_REMOVE(&store_mem_queue, pkt, pkt_mem_link); + store_mem_size -= pkt->pkt_payloadlen; + } + + pkt->pkt_storage = NULL; + } + pkt_deref(pkt); +} + + +/* + * + */ +void +pkt_load(th_pkt_t *pkt) +{ + if(pkt->pkt_payload == NULL && pkt->pkt_storage != NULL) { + pkt->pkt_payload = malloc(pkt->pkt_payloadlen); + pread(pkt->pkt_storage->ts_fd, pkt->pkt_payload, pkt->pkt_payloadlen, + pkt->pkt_storage_offset); + storage_mem_enq(pkt); + } + if(pkt->pkt_payload == NULL) + printf("Packet %p load failed\n", pkt); +} + + + +/* + * + */ +static void +storage_deref(th_storage_t *s) +{ + if(s->ts_refcount > 1) { + s->ts_refcount--; + return; + } + if(curstore == s) + curstore = NULL; + + close(s->ts_fd); + unlink(s->ts_filename); + free(s->ts_filename); + free(s); +} + + + + +/* + * + */ +static void +storage_mem_enq(th_pkt_t *pkt) +{ + TAILQ_INSERT_TAIL(&store_mem_queue, pkt, pkt_mem_link); + store_mem_size += pkt->pkt_payloadlen; + + while(store_mem_size >= store_mem_size_max) { + pkt = TAILQ_FIRST(&store_mem_queue); + + TAILQ_REMOVE(&store_mem_queue, pkt, pkt_mem_link); + store_mem_size -= pkt->pkt_payloadlen; + + free(pkt->pkt_payload); + pkt->pkt_payload = NULL; + } +} + + + + +/* + * + */ +static void +storage_disk_enq(th_pkt_t *pkt) +{ + th_storage_t *s; + char fbuf[500]; + int fd; + + TAILQ_INSERT_TAIL(&store_disk_queue, pkt, pkt_disk_link); + store_disk_size += pkt->pkt_payloadlen; + + if(curstore == NULL) { + snprintf(fbuf, sizeof(fbuf), "%s/s%d", store_path, ++store_tally); + + fd = open(fbuf, O_RDWR | O_CREAT | O_TRUNC, 0644); + if(fd == -1) { + s = NULL; + } else { + s = calloc(1, sizeof(th_storage_t)); + s->ts_fd = fd; + s->ts_filename = strdup(fbuf); + } + curstore = s; + } else { + s = curstore; + } + + + if(s != NULL) { + s->ts_refcount++; + pkt->pkt_storage = s; + pkt->pkt_storage_offset = s->ts_offset; + s->ts_offset += pkt->pkt_payloadlen; + if(s->ts_offset > store_chunk_size) + curstore = NULL; + } + + while(store_disk_size > store_disk_size_max) { + pkt = TAILQ_FIRST(&store_disk_queue); + if(pkt->pkt_refcount > 1) + printf("UNSTORE of reference packet %p\n", pkt); + pkt_unstore(pkt); + } +} + + + + + + + + + +/** + * Erase all old files + */ +static void +storage_wipe(void) +{ + DIR *dir; + struct dirent *d; + char fbuf[500]; + + if((dir = opendir(store_path)) != NULL) { + + while((d = readdir(dir)) != NULL) { + if(d->d_name[0] == '.') + continue; + + snprintf(fbuf, sizeof(fbuf), "%s/%s", store_path, d->d_name); + unlink(fbuf); + } + } + closedir(dir); +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 00000000..74b260f5 --- /dev/null +++ b/buffer.h @@ -0,0 +1,52 @@ +/* + * Packet / Buffer management + * 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 BUFFER_H_ +#define BUFFER_H_ + +th_pkt_t *pkt_ref(th_pkt_t *pkt); + +void pkt_deref(th_pkt_t *pkt); + +void pkt_init(void); + +th_pkt_t *pkt_alloc(void *data, size_t datalen, int64_t pts, int64_t dts); + +th_pkt_t *pkt_copy(th_pkt_t *pkt); + +void pkt_store(th_pkt_t *pkt); + +void pkt_unstore(th_pkt_t *pkt); + +void pkt_load(th_pkt_t *pkt); + +void *pkt_payload(th_pkt_t *pkt); + +size_t pkt_len(th_pkt_t *pkt); + +extern int64_t store_mem_size; +extern int64_t store_mem_size_max; +extern int64_t store_disk_size; +extern int64_t store_disk_size_max; +extern int store_packets; + + +#define pkt_payload(pkt) ((pkt)->pkt_payload) +#define pkt_len(pkt) ((pkt)->pkt_payloadlen) + +#endif /* BUFFER_H_ */ diff --git a/channels.c b/channels.c index 8719227f..d560f4df 100644 --- a/channels.c +++ b/channels.c @@ -78,7 +78,7 @@ transportcmp(th_transport_t *a, th_transport_t *b) int transport_set_channel(th_transport_t *t, th_channel_t *ch) { - th_pid_t *tp; + th_stream_t *st; char *chname; t->tht_channel = ch; @@ -90,9 +90,9 @@ transport_set_channel(th_transport_t *t, th_channel_t *ch) t->tht_name, chname); free(chname); - LIST_FOREACH(tp, &t->tht_pids, tp_link) - syslog(LOG_DEBUG, " Pid %5d [%s]", - tp->tp_pid, htstvstreamtype2txt(tp->tp_type)); + LIST_FOREACH(st, &t->tht_streams, st_link) + syslog(LOG_DEBUG, " Stream [%s] - pid %d", + htstvstreamtype2txt(st->st_type), st->st_pid); return 0; } @@ -111,6 +111,7 @@ service_load(struct config_head *head) return; t = calloc(1, sizeof(th_transport_t)); + t->tht_prio = atoi(config_get_str_sub(head, "prio", "")); if(0) { diff --git a/dispatch.c b/dispatch.c index 3ac0b5a7..f4a8df0b 100644 --- a/dispatch.c +++ b/dispatch.c @@ -29,6 +29,9 @@ #include "dispatch.h" +#define EPOLL_FDS_PER_ROUND 100 +time_t dispatch_clock; + static int epoll_fd; typedef struct epoll_entry { @@ -39,83 +42,69 @@ typedef struct epoll_entry { int refcnt; } epoll_entry_t; - - -typedef struct stimer { - LIST_ENTRY(stimer) link; - void (*callback)(void *aux); - void *aux; - int64_t t; -} stimer_t; - -LIST_HEAD(, stimer) dispatch_timers; - +LIST_HEAD(, dtimer) dispatch_timers; static int -stimercmp(stimer_t *a, stimer_t *b) +stimercmp(dtimer_t *a, dtimer_t *b) { - if(a->t < b->t) + if(a->dti_expire < b->dti_expire) return -1; - else if(a->t > b->t) + else if(a->dti_expire > b->dti_expire) return 1; return 0; } -void * -stimer_add_hires(void (*callback)(void *aux), void *aux, int64_t t) +void +dtimer_arm_hires(dtimer_t *ti, dti_callback_t *callback, void *aux, int64_t t) { - stimer_t *ti = malloc(sizeof(stimer_t)); - ti->t = t; - ti->aux = aux; - ti->callback = callback; - LIST_INSERT_SORTED(&dispatch_timers, ti, link, stimercmp); - return ti; + if(ti->dti_callback != NULL) + LIST_REMOVE(ti, dti_link); + ti->dti_expire = t; + ti->dti_opaque = aux; + ti->dti_callback = callback; + LIST_INSERT_SORTED(&dispatch_timers, ti, dti_link, stimercmp); } - -void * -stimer_add(void (*callback)(void *aux), void *aux, int delta) +void +dtimer_arm(dtimer_t *ti, dti_callback_t *callback, void *opaque, int delta) { time_t now; time(&now); - return stimer_add_hires(callback, aux, (uint64_t)(now + delta) * 1000000ULL); + dtimer_arm_hires(ti, callback, opaque, + (uint64_t)(now + delta) * 1000000ULL); } -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); - int64_t delta; + dtimer_t *ti = LIST_FIRST(&dispatch_timers); + int delta; if(ti == NULL) return -1; - delta = ti->t - getclock_hires(); - return delta < 0 ? 0 : delta / 1000ULL; + delta = (ti->dti_expire - getclock_hires() + 999) / 1000; + + return delta > 0 ? delta : 0; } static void stimer_dispatch(int64_t now) { - stimer_t *ti; + dtimer_t *ti; + dti_callback_t *cb; while((ti = LIST_FIRST(&dispatch_timers)) != NULL) { - if(ti->t > now) + if(ti->dti_expire > now + 100ULL) break; - LIST_REMOVE(ti, link); - ti->callback(ti->aux); - free(ti); + LIST_REMOVE(ti, dti_link); + cb = ti->dti_callback; + ti->dti_callback = NULL; + cb(ti->dti_opaque, now); } } @@ -213,21 +202,19 @@ dispatch_delfd(void *handle) -#define EPOLL_FDS_PER_ROUND 100 - -time_t dispatch_clock; void dispatcher(void) { struct epoll_entry *e; - int i, n; + int i, n, delta; struct timeval tv; int64_t now; static struct epoll_event events[EPOLL_FDS_PER_ROUND]; - n = epoll_wait(epoll_fd, events, EPOLL_FDS_PER_ROUND, stimer_next()); + delta = stimer_next(); + n = epoll_wait(epoll_fd, events, EPOLL_FDS_PER_ROUND, delta); gettimeofday(&tv, NULL); @@ -247,9 +234,9 @@ dispatcher(void) e->callback(((events[i].events & (EPOLLERR | EPOLLHUP)) ? DISPATCH_ERR : 0 ) | - ((events[i].events & EPOLLIN) ? DISPATCH_READ : 0 ) | + ((events[i].events & EPOLLIN) ? DISPATCH_READ : 0 ) | ((events[i].events & EPOLLOUT) ? DISPATCH_WRITE : 0 ) | - ((events[i].events & EPOLLPRI) ? DISPATCH_PRI : 0 ), + ((events[i].events & EPOLLPRI) ? DISPATCH_PRI : 0 ), e->opaque, e->fd); } diff --git a/dispatch.h b/dispatch.h index 26fbd12f..4b51c522 100644 --- a/dispatch.h +++ b/dispatch.h @@ -20,6 +20,7 @@ #define DISPATCH_H #include +#include "tvhead.h" extern time_t dispatch_clock; @@ -48,8 +49,20 @@ void dispatch_set(void *handle, int flags); void dispatch_clr(void *handle, int flags); void dispatcher(void); -void *stimer_add(void (*callback)(void *aux), void *aux, int delta); -void *stimer_add_hires(void (*callback)(void *aux), void *aux, int64_t t); -void stimer_del(void *handle); +void dtimer_arm(dtimer_t *ti, dti_callback_t *callback, void *aux, int delta); +void dtimer_arm_hires(dtimer_t *ti, dti_callback_t *callback, + void *aux, int64_t t); + + +extern inline void +dtimer_disarm(dtimer_t *ti) +{ + if(ti->dti_callback) { + LIST_REMOVE(ti, dti_link); + ti->dti_callback = NULL; + } +} + +#define dtimer_isarmed(ti) ((ti)->dti_callback ? 1 : 0) #endif /* DISPATCH_H */ diff --git a/dvb.c b/dvb.c index ed0258b8..fb2f0499 100644 --- a/dvb.c +++ b/dvb.c @@ -61,9 +61,9 @@ 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_mux_scanner(void *aux); +static void dvb_mux_scanner(void *aux, int64_t now); -static void dvb_fec_monitor(void *aux); +static void dvb_fec_monitor(void *aux, int64_t now); static void dvb_frontend_event(int events, void *opaque, int fd) @@ -149,14 +149,14 @@ dvb_add_adapter(const char *path) pthread_mutex_init(&tda->tda_mux_lock, NULL); LIST_INSERT_HEAD(&dvb_adapters_probing, tda, tda_link); + startupcounter++; tda->tda_name = strdup(tda->tda_fe_info.name); dispatch_addfd(tda->tda_fe_fd, dvb_frontend_event, tda, DISPATCH_PRI); syslog(LOG_INFO, "Adding adapter %s (%s)", tda->tda_fe_info.name, path); - stimer_add(dvb_fec_monitor, tda, 1); - + dtimer_arm(&tda->tda_fec_monitor_timer, dvb_fec_monitor, tda, 1); } @@ -199,6 +199,7 @@ tdt_destroy(th_dvb_table_t *tdt) { LIST_REMOVE(tdt, tdt_link); close(dispatch_delfd(tdt->tdt_handle)); + free(tdt->tdt_name); free(tdt); } @@ -254,11 +255,12 @@ static void 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) + int initial_count, char *name) { th_dvb_table_t *tdt = malloc(sizeof(th_dvb_table_t)); LIST_INSERT_HEAD(&tdmi->tdmi_tables, tdt, tdt_link); + tdt->tdt_name = strdup(name); tdt->tdt_callback = callback; tdt->tdt_opaque = opaque; tdt->tdt_tdmi = tdmi; @@ -386,7 +388,7 @@ dvb_find_transport(th_dvb_mux_instance_t *tdmi, uint16_t nid, uint16_t tid, return NULL; } - tdt_add(tdmi, fd, dvb_service_callback, t, 0); + tdt_add(tdmi, fd, dvb_service_callback, t, 0, "PMT"); t->tht_name = strdup(tdm->tdm_title); LIST_INSERT_HEAD(&all_transports, t, tht_global_link); return t; @@ -486,7 +488,7 @@ dvb_tdt_add_demux(th_dvb_mux_instance_t *tdmi) close(fd); return; } - tdt_add(tdmi, fd, dvb_tdt_callback, NULL, 1); + tdt_add(tdmi, fd, dvb_tdt_callback, NULL, 1, "tdt"); } @@ -690,7 +692,7 @@ dvb_eit_add_demux(th_dvb_mux_instance_t *tdmi) return; } - tdt_add(tdmi, fd, dvb_eit_callback, NULL, 1); + tdt_add(tdmi, fd, dvb_eit_callback, NULL, 1, "eit"); } @@ -778,13 +780,15 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, 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); - if(LIST_FIRST(&t->tht_pids) != NULL && t->tht_channel == NULL) - ret |= transport_set_channel(t, channel_find(chname, 1)); + if(LIST_FIRST(&t->tht_streams) != NULL && t->tht_channel == NULL) { + transport_set_channel(t, channel_find(chname, 1)); + } else { + ret |= 1; + } } } return ret; @@ -815,7 +819,7 @@ dvb_sdt_add_demux(th_dvb_mux_instance_t *tdmi) return; } - tdt_add(tdmi, fd, dvb_sdt_callback, NULL, 0); + tdt_add(tdmi, fd, dvb_sdt_callback, NULL, 0, "sdt"); } @@ -824,10 +828,7 @@ 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; - } + dtimer_disarm(&tdmi->tdmi_initial_scan_timer); tdmi->tdmi_state = TDMI_IDLE; @@ -839,13 +840,14 @@ tdmi_activate(th_dvb_mux_instance_t *tdmi) tdmi = LIST_FIRST(&tda->tda_muxes_configured); if(tdmi == NULL) { + startupcounter--; 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); - stimer_add(dvb_mux_scanner, tda, 10); + dtimer_arm(&tda->tda_mux_scanner_timer, dvb_mux_scanner, tda, 10); return; } dvb_start_initial_scan(tdmi); @@ -853,13 +855,13 @@ tdmi_activate(th_dvb_mux_instance_t *tdmi) static void -tdmi_initial_scan_timeout(void *aux) +tdmi_initial_scan_timeout(void *aux, int64_t now) { th_dvb_mux_instance_t *tdmi = aux; th_dvb_adapter_t *tda = tdmi->tdmi_adapter; const char *err; - tdmi->tdmi_initial_scan_timer = NULL; + dtimer_disarm(&tdmi->tdmi_initial_scan_timer); err = "Unknown error"; @@ -882,6 +884,7 @@ tdmi_check_scan_status(th_dvb_mux_instance_t *tdmi) if(tdmi->tdmi_state >= TDMI_IDLE) return; + LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link) if(tdt->tdt_count == 0) @@ -902,8 +905,8 @@ dvb_start_initial_scan(th_dvb_mux_instance_t *tdmi) { dvb_tune_tdmi(tdmi, 1, TDMI_INITIAL_SCAN); - tdmi->tdmi_initial_scan_timer = - stimer_add(tdmi_initial_scan_timeout, tdmi, 5); + dtimer_arm(&tdmi->tdmi_initial_scan_timer, + tdmi_initial_scan_timeout, tdmi, 5); } @@ -920,14 +923,15 @@ mux_sort_quality(th_dvb_mux_instance_t *a, th_dvb_mux_instance_t *b) static void -dvb_fec_monitor(void *aux) +dvb_fec_monitor(void *aux, int64_t now) { th_dvb_adapter_t *tda = aux; th_dvb_mux_instance_t *tdmi; th_dvb_mux_t *tdm; int v; - stimer_add(dvb_fec_monitor, tda, 1); + dtimer_arm(&tda->tda_fec_monitor_timer, dvb_fec_monitor, tda, 1); + tdmi = tda->tda_mux_current; if(tdmi != NULL && tdmi->tdmi_status == NULL) { @@ -959,12 +963,12 @@ dvb_fec_monitor(void *aux) */ static void -dvb_mux_scanner(void *aux) +dvb_mux_scanner(void *aux, int64_t now) { th_dvb_adapter_t *tda = aux; th_dvb_mux_instance_t *tdmi; - stimer_add(dvb_mux_scanner, tda, 10); + dtimer_arm(&tda->tda_mux_scanner_timer, dvb_mux_scanner, tda, 10); if(transport_compute_weight(&tda->tda_transports) > 0) return; /* someone is here */ diff --git a/dvb_dvr.c b/dvb_dvr.c index d439ba01..50113fb5 100644 --- a/dvb_dvr.c +++ b/dvb_dvr.c @@ -41,8 +41,7 @@ #include "channels.h" #include "transports.h" #include "dvb_support.h" -#include "ts.h" - +#include "tsdemux.h" static void dvr_fd_callback(int events, void *opaque, int fd); @@ -75,7 +74,7 @@ dvb_dvr_process_packets(th_dvb_adapter_t *tda, uint8_t *tsb, int r) while(r >= 188) { pid = (tsb[1] & 0x1f) << 8 | tsb[2]; LIST_FOREACH(t, &tda->tda_transports, tht_adapter_link) - ts_recv_tsb(t, pid, tsb, 1, AV_NOPTS_VALUE); + ts_recv_packet(t, pid, tsb); r -= 188; tsb += 188; } @@ -121,13 +120,13 @@ dvb_adapter_clean(th_dvb_adapter_t *tda) void dvb_stop_feed(th_transport_t *t) { - th_pid_t *tp; + th_stream_t *st; 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; + LIST_FOREACH(st, &t->tht_streams, st_link) { + close(st->st_demuxer_fd); + st->st_demuxer_fd = -1; } t->tht_status = TRANSPORT_IDLE; transport_flush_subscribers(t); @@ -142,7 +141,7 @@ dvb_start_feed(th_transport_t *t, unsigned int weight) { th_dvb_adapter_t *tda; struct dmx_pes_filter_params dmx_param; - th_pid_t *tp; + th_stream_t *st; int w, fd, pid; th_dvb_mux_instance_t *tdmi, *cand = NULL; @@ -174,15 +173,15 @@ dvb_start_feed(th_transport_t *t, unsigned int weight) gotmux: tda = tdmi->tdmi_adapter; - LIST_FOREACH(tp, &t->tht_pids, tp_link) { + LIST_FOREACH(st, &t->tht_streams, st_link) { fd = open(tda->tda_demux_path, O_RDWR); - pid = tp->tp_pid; - tp->tp_cc_valid = 0; + pid = st->st_pid; + st->st_cc_valid = 0; if(fd == -1) { - tp->tp_demuxer_fd = -1; + st->st_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)); @@ -204,7 +203,7 @@ dvb_start_feed(th_transport_t *t, unsigned int weight) fd = -1; } - tp->tp_demuxer_fd = fd; + st->st_demuxer_fd = fd; } LIST_INSERT_HEAD(&tda->tda_transports, t, tht_adapter_link); diff --git a/epg.c b/epg.c index f4bfec11..5c8320dc 100644 --- a/epg.c +++ b/epg.c @@ -33,6 +33,7 @@ static pthread_mutex_t epg_mutex = PTHREAD_MUTEX_INITIALIZER; struct event_list epg_hash[EPG_HASH_ID_WIDTH]; +static dtimer_t epg_channel_maintain_timer; void epg_lock(void) @@ -420,13 +421,13 @@ epg_locate_current_event(th_channel_t *ch, time_t now) static void -epg_channel_maintain(void *aux) +epg_channel_maintain(void *aux, int64_t clk) { th_channel_t *ch; event_t *e, *cur; time_t now; - stimer_add(epg_channel_maintain, NULL, 5); + dtimer_arm(&epg_channel_maintain_timer, epg_channel_maintain, NULL, 5); now = dispatch_clock; @@ -502,6 +503,6 @@ epg_transfer_events(th_channel_t *ch, struct event_queue *src, void epg_init(void) { - stimer_add(epg_channel_maintain, NULL, 5); + dtimer_arm(&epg_channel_maintain_timer, epg_channel_maintain, NULL, 5); } diff --git a/htsclient.c b/htsclient.c index b9946d3a..3972be14 100644 --- a/htsclient.c +++ b/htsclient.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -37,6 +38,8 @@ #include "dispatch.h" #include "dvb.h" #include "strtab.h" +#include "buffer.h" +#include "tsmux.h" LIST_HEAD(client_list, client); @@ -70,13 +73,15 @@ typedef struct client { void *c_dispatch_handle; - void *c_status_timer; + dtimer_t c_status_timer; + + void *c_muxer; } client_t; -static void client_status_update(void *aux); +static void client_status_update(void *aux, int64_t now); static void cprintf(client_t *c, const char *fmt, ...) @@ -92,6 +97,46 @@ cprintf(client_t *c, const char *fmt, ...) } +void +client_output_ts(void *opaque, th_subscription_t *s, + uint8_t *pkt, int blocks, int64_t pcr) +{ + struct msghdr msg; + struct iovec vec[2]; + int r; + client_t *c = opaque; + struct sockaddr_in sin; + char hdr[2]; + + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(c->c_port); + sin.sin_addr = c->c_ipaddr; + + hdr[0] = HTSTV_TRANSPORT_STREAM; + hdr[1] = s->ths_channel->ch_index; + + vec[0].iov_base = hdr; + vec[0].iov_len = 2; + vec[1].iov_base = pkt; + vec[1].iov_len = blocks * 188; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sin; + msg.msg_namelen = sizeof(struct sockaddr_in); + msg.msg_iov = vec; + msg.msg_iovlen = 2; + + r = sendmsg(c->c_streamfd, &msg, 0); + if(r < 0) + perror("sendmsg"); +} + + + + +#if 0 static void client_ip_streamer(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi, int64_t pcr) @@ -136,6 +181,7 @@ client_ip_streamer(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi, s->ths_pkt_ptr = 2; } } +#endif /* * @@ -360,6 +406,18 @@ cr_show(client_t *c, char **argv, int argc) return 0; } + if(!strcasecmp(subcmd, "storage")) { + cprintf(c, "In-memory storage %lld / %lld\n", + store_mem_size, store_mem_size_max); + + cprintf(c, " On-disk storage %lld / %lld\n", + store_disk_size, store_disk_size_max); + + cprintf(c, " %d packets in memory\n", + store_packets); + return 0; + } + return 1; } @@ -419,10 +477,34 @@ cr_channel_unsubscribe(client_t *c, char **argv, int argc) return 0; } + +/* + * Called when a subscription gets/loses access to a transport + */ +static void +client_subscription_callback(struct th_subscription *s, + subscription_event_t event, void *opaque) +{ + client_t *c = opaque; + + switch(event) { + case TRANSPORT_AVAILABLE: + assert(c->c_muxer == NULL); + c->c_muxer = ts_muxer_init(s, client_output_ts, c, TM_HTSCLIENTMODE); + ts_muxer_play(c->c_muxer, 0); + break; + + case TRANSPORT_UNAVAILABLE: + assert(c->c_muxer != NULL); + ts_muxer_deinit(c->c_muxer); + c->c_muxer = NULL; + break; + } +} + /* * */ - int cr_channel_subscribe(client_t *c, char **argv, int argc) { @@ -447,7 +529,8 @@ cr_channel_subscribe(client_t *c, char **argv, int argc) if((ch = channel_by_index(chindex)) == NULL) return 1; - s = subscription_create(ch, c, client_ip_streamer, weight, "client"); + s = subscription_create(ch, weight, "client", + client_subscription_callback, c); if(s == NULL) return 1; @@ -757,7 +840,7 @@ client_teardown(client_t *c, int err) syslog(LOG_INFO, "%s disconnected -- %s", c->c_title, strerror(err)); - stimer_del(c->c_status_timer); + dtimer_disarm(&c->c_status_timer); dispatch_delfd(c->c_dispatch_handle); @@ -903,7 +986,7 @@ client_connect_callback(int events, void *opaque, int fd) c->c_dispatch_handle = dispatch_addfd(newfd, client_socket_callback, c, DISPATCH_READ); - c->c_status_timer = stimer_add(client_status_update, c, 1); + dtimer_arm(&c->c_status_timer, client_status_update, c, 1); } @@ -981,7 +1064,7 @@ csprintf(client_t *c, th_channel_t *ch, const char *fmt, ...) static void -client_status_update(void *aux) +client_status_update(void *aux, int64_t now) { client_t *c = aux; th_channel_t *ch; @@ -992,7 +1075,7 @@ client_status_update(void *aux) th_transport_t *t; int ccerr, rate; - c->c_status_timer = stimer_add(client_status_update, c, 1); + dtimer_arm(&c->c_status_timer, client_status_update, c, 1); LIST_FOREACH(s, &c->c_subscriptions, ths_subscriber_link) { diff --git a/iptv_input.c b/iptv_input.c index ec8fd7ee..f94f10fe 100644 --- a/iptv_input.c +++ b/iptv_input.c @@ -40,14 +40,14 @@ #include "transports.h" #include "dispatch.h" #include "psi.h" -#include "ts.h" +#include "tsdemux.h" static struct th_transport_list iptv_probing_transports; static struct th_transport_list iptv_stale_transports; -static void *iptv_probe_timer; +static dtimer_t iptv_probe_timer; static void iptv_probe_transport(th_transport_t *t); -static void iptv_probe_callback(void *aux); +static void iptv_probe_callback(void *aux, int64_t now); static void iptv_probe_done(th_transport_t *t, int timeout); static void @@ -62,7 +62,7 @@ iptv_fd_callback(int events, void *opaque, int fd) while(r >= 188) { pid = (tsb[1] & 0x1f) << 8 | tsb[2]; - ts_recv_tsb(t, pid, tsb, 1, AV_NOPTS_VALUE); + ts_recv_packet(t, pid, tsb); r -= 188; tsb += 188; } @@ -136,7 +136,7 @@ iptv_stop_feed(th_transport_t *t) */ static void -iptv_parse_pmt(struct th_transport *t, struct th_pid *pi, +iptv_parse_pmt(struct th_transport *t, th_stream_t *st, uint8_t *table, int table_len) { if(table[0] != 2 || t->tht_status != TRANSPORT_PROBING) @@ -153,7 +153,7 @@ iptv_parse_pmt(struct th_transport *t, struct th_pid *pi, */ static void -iptv_parse_pat(struct th_transport *t, struct th_pid *pi, +iptv_parse_pat(struct th_transport *t, th_stream_t *st, uint8_t *table, int table_len) { if(table[0] != 0 || t->tht_status != TRANSPORT_PROBING) @@ -175,7 +175,7 @@ iptv_configure_transport(th_transport_t *t, const char *iptv_type, char buf[100]; char ifname[100]; struct ifreq ifr; - th_pid_t *pi; + th_stream_t *st; if(!strcasecmp(iptv_type, "rawudp")) t->tht_iptv_mode = IPTV_MODE_RAWUDP; @@ -222,15 +222,16 @@ iptv_configure_transport(th_transport_t *t, const char *iptv_type, ifname, inet_ntoa(t->tht_iptv_group_addr), t->tht_iptv_port); t->tht_name = strdup(buf); - pi = ts_add_pid(t, 0, HTSTV_TABLE); - pi->tp_got_section = iptv_parse_pat; + st = transport_add_stream(t, 0, HTSTV_TABLE); + st->st_got_section = iptv_parse_pat; t->tht_channel = channel_find(channel_name, 1); LIST_INSERT_HEAD(&iptv_probing_transports, t, tht_adapter_link); + startupcounter++; - if(iptv_probe_timer == NULL) { + if(!dtimer_isarmed(&iptv_probe_timer)) { iptv_probe_transport(t); - iptv_probe_timer = stimer_add(iptv_probe_callback, t, 5); + dtimer_arm(&iptv_probe_timer, iptv_probe_callback, t, 5); } return 0; @@ -248,12 +249,13 @@ static void iptv_probe_done(th_transport_t *t, int timeout) { int pidcnt = 0; - th_pid_t *tp; + th_stream_t *st; - if(!timeout) - stimer_del(iptv_probe_timer); + startupcounter--; - LIST_FOREACH(tp, &t->tht_pids, tp_link) + dtimer_disarm(&iptv_probe_timer); + + LIST_FOREACH(st, &t->tht_streams, st_link) pidcnt++; LIST_REMOVE(t, tht_adapter_link); @@ -269,19 +271,17 @@ iptv_probe_done(th_transport_t *t, int timeout) LIST_INSERT_HEAD(&iptv_stale_transports, t, tht_adapter_link); t = LIST_FIRST(&iptv_probing_transports); - if(t == NULL) { - iptv_probe_timer = NULL; + if(t == NULL) return; - } iptv_probe_transport(t); - iptv_probe_timer = stimer_add(iptv_probe_callback, t, 5); + dtimer_arm(&iptv_probe_timer, iptv_probe_callback, t, 5); } static void -iptv_probe_callback(void *aux) +iptv_probe_callback(void *aux, int64_t now) { th_transport_t *t = aux; iptv_probe_done(t, 1); diff --git a/iptv_output.c b/iptv_output.c index 6ba85297..57ee797e 100644 --- a/iptv_output.c +++ b/iptv_output.c @@ -42,6 +42,7 @@ typedef struct output_multicast { #define MULTICAST_PKT_SIZ (188 * 7) +#if 0 static void om_ip_streamer(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi, @@ -74,6 +75,7 @@ om_ip_streamer(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi, s->ths_pkt_ptr = 0; } } +#endif @@ -135,8 +137,7 @@ output_multicast_load(struct config_head *head) syslog(LOG_INFO, "Static multicast output: \"%s\" to %s, source %s ", ch->ch_name, title, inet_ntoa(sin.sin_addr)); - subscription_create(ch, om, om_ip_streamer, 900, title); - + // subscription_create(ch, 900, title); return; err: diff --git a/main.c b/main.c index 83f686f4..1154c30b 100644 --- a/main.c +++ b/main.c @@ -49,9 +49,11 @@ #include "subscriptions.h" #include "iptv_output.h" #include "rtsp.h" +#include "buffer.h" int running; int xmltvreload; +int startupcounter; static pthread_mutex_t tag_mutex = PTHREAD_MUTEX_INITIALIZER; static uint32_t tag_tally; @@ -165,10 +167,10 @@ main(int argc, char **argv) av_register_all(); av_log_set_level(AV_LOG_INFO); - client_start(); - dvb_init(); + pkt_init(); - v4l_add_adapters(); + dvb_init(); + v4l_init(); channels_load(); @@ -176,17 +178,23 @@ main(int argc, char **argv) xmltv_init(); pvr_init(); - output_multicast_setup(); subscriptions_init(); - - rtsp_start(); running = 1; + while(running) { + if(startupcounter == 0) { + startupcounter = -1; + syslog(LOG_NOTICE, + "Initial input setup completed, starting output modules"); - while(running) + rtsp_start(); + output_multicast_setup(); + client_start(); + } dispatcher(); + } syslog(LOG_NOTICE, "Exiting HTS TV Headend"); @@ -281,3 +289,22 @@ utf8tofilename(const char *in) return convert_to(in, "LATIN1"); } + + + + + +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"; + case HTSTV_TABLE: return "PSI table"; + default: return ""; + } +} diff --git a/pes.c b/pes.c new file mode 100644 index 00000000..dff7cc36 --- /dev/null +++ b/pes.c @@ -0,0 +1,332 @@ +/* + * PES parsing 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 "tvhead.h" +#include "pes.h" +#include "dispatch.h" +#include "buffer.h" + +static void pes_compute_dts(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt); +static void pes_compute_pts(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt); +static void pes_compute_duration(th_transport_t *t, th_stream_t *st, + th_pkt_t *pkt); + + +#define getu32(b, l) ({ \ + uint32_t x = (b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]); \ + b+=4; \ + l-=4; \ + x; \ +}) + +#define getu16(b, l) ({ \ + uint16_t x = (b[0] << 8 | b[1]); \ + b+=2; \ + l-=2; \ + x; \ +}) + +#define getu8(b, l) ({ \ + uint8_t x = b[0]; \ + b+=1; \ + l-=1; \ + x; \ +}) + +#define getpts(b, l) ({ \ + int64_t _pts; \ + _pts = (int64_t)((getu8(b, l) >> 1) & 0x07) << 30; \ + _pts |= (int64_t)(getu16(b, l) >> 1) << 15; \ + _pts |= (int64_t)(getu16(b, l) >> 1); \ + _pts; \ +}) + + + + +/* + * pes_packet_input() + * + * return 0 if buf-memory is claimed by us, -1 if memory can be resued + */ +int +pes_packet_input(th_transport_t *t, th_stream_t *st, uint8_t *buf, size_t len) +{ + int64_t dts = AV_NOPTS_VALUE, pts = AV_NOPTS_VALUE, ts, ptsoff; + uint8_t *outbuf; + int hdr, flags, hlen, rlen, outlen; + th_pkt_t *pkt; + AVRational mpeg_tc = {1, 90000}; + + hdr = getu8(buf, len); + flags = getu8(buf, len); + hlen = getu8(buf, len); + + if(len < hlen || (hdr & 0xc0) != 0x80) + return -1; + + if((flags & 0xc0) == 0xc0) { + if(hlen < 10) + return -1; + + pts = getpts(buf, len); + dts = getpts(buf, len); + hlen -= 10; + + } else if((flags & 0xc0) == 0x80) { + if(hlen < 5) + return -1; + + dts = pts = getpts(buf, len); + hlen -= 5; + } + + buf += hlen; + len -= hlen; + + if(t->tht_dts_start == AV_NOPTS_VALUE) { + if(dts == AV_NOPTS_VALUE) + return -1; + + t->tht_dts_start = dts; + } + + if(dts != AV_NOPTS_VALUE) { + ptsoff = (int32_t)pts - (int32_t)dts; + dts -= t->tht_dts_start; + dts &= 0x1ffffffffULL; + ts = dts + ((int64_t)st->st_dts_u << 33); + if((ts < 0 || ts > 10000000) && st->st_dts == AV_NOPTS_VALUE) + return -1; + + if(ts < st->st_dts) { + st->st_dts_u++; + ts = dts + ((int64_t)st->st_dts_u << 33); + } + + st->st_dts = dts; + + pts = dts + ptsoff; + + dts = av_rescale_q(dts, mpeg_tc, AV_TIME_BASE_Q); + pts = av_rescale_q(pts, mpeg_tc, AV_TIME_BASE_Q); + } + + while(len > 0) { + rlen = av_parser_parse(st->st_parser, st->st_ctx, + &outbuf, &outlen, buf, len, pts, dts); + + if(outlen) { + pkt = pkt_alloc(outbuf, outlen, st->st_parser->pts, st->st_parser->dts); + pkt->pkt_commercial = t->tht_tt_commercial_advice; + + pes_compute_dts(t, st, pkt); + dts = AV_NOPTS_VALUE; + pts = AV_NOPTS_VALUE; + } + buf += rlen; + len -= rlen; + } + return -1; +} + +/* + * If DTS is unknown we hold packets until we see a packet with correct + * DTS, then we do linear interpolation. + */ +static void +pes_compute_dts(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt) +{ + int64_t dts, d_dts, v; + + if(pkt->pkt_dts == AV_NOPTS_VALUE) { + st->st_dtsq_len++; + pkt->pkt_pts = AV_NOPTS_VALUE; + TAILQ_INSERT_TAIL(&st->st_dtsq, pkt, pkt_queue_link); + return; + } + + if(TAILQ_FIRST(&st->st_dtsq) == NULL) { + st->st_last_dts = pkt->pkt_dts; + pes_compute_pts(t, st, pkt); + return; + } + TAILQ_INSERT_TAIL(&st->st_dtsq, pkt, pkt_queue_link); + v = st->st_dtsq_len + 1; + d_dts = (pkt->pkt_dts - st->st_last_dts) / v; + dts = st->st_last_dts; + + while((pkt = TAILQ_FIRST(&st->st_dtsq)) != NULL) { + dts += d_dts; + pkt->pkt_dts = dts; + pkt->pkt_pts = AV_NOPTS_VALUE; + + TAILQ_REMOVE(&st->st_dtsq, pkt, pkt_queue_link); + pes_compute_pts(t, st, pkt); + } + st->st_dtsq_len = 0; + st->st_last_dts = dts; +} + + +/* + * Compute PTS of packets. + * + * It seems some providers send TV without PTS/DTS for all video + * frames, so we need to reconstruct that here. + * + */ +static void +pes_compute_pts(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt) +{ + th_pkt_t *p; + uint8_t *buf; + uint32_t sc; + + if(pkt->pkt_pts == AV_NOPTS_VALUE) { + + /* PTS not known, figure it out */ + + switch(st->st_type) { + case HTSTV_MPEG2VIDEO: + + /* Figure frame type */ + + if(pkt_len(pkt) < 6) { + pkt_deref(pkt); + return; + } + + buf = pkt_payload(pkt); + sc = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; + + if(sc == 0x100) { /* PICTURE START CODE */ + pkt->pkt_frametype = (buf[5] >> 3) & 7; + } else { + pkt->pkt_frametype = PKT_I_FRAME; + } + + if(pkt->pkt_frametype < PKT_I_FRAME || + pkt->pkt_frametype > PKT_B_FRAME) { + pkt_deref(pkt); + return; + } + + TAILQ_INSERT_TAIL(&st->st_ptsq, pkt, pkt_queue_link); + st->st_ptsq_len++; + + while((pkt = TAILQ_FIRST(&st->st_ptsq)) != NULL) { + switch(pkt->pkt_frametype) { + case PKT_B_FRAME: + /* B-frames have same PTS as DTS, pass them on */ + pkt->pkt_pts = pkt->pkt_dts; + break; + + case PKT_I_FRAME: + case PKT_P_FRAME: + /* Presentation occures at DTS of next I or P frame, + try to find it */ + p = TAILQ_NEXT(pkt, pkt_queue_link); + while(1) { + if(p == NULL) + return; /* not arrived yet, wait */ + if(p->pkt_frametype <= PKT_P_FRAME) { + pkt->pkt_pts = p->pkt_dts; + break; + } + p = TAILQ_NEXT(p, pkt_queue_link); + } + break; + } + + TAILQ_REMOVE(&st->st_ptsq, pkt, pkt_queue_link); + st->st_ptsq_len--; + pes_compute_duration(t, st, pkt); + } + return; + + case HTSTV_H264: + /* For h264, we cannot do anything (yet) */ + pkt->pkt_pts = pkt->pkt_dts; /* this is wrong */ + break; + + default: + /* Rest is audio, no decoder delay */ + pkt->pkt_pts = pkt->pkt_dts; + break; + } + } + pes_compute_duration(t, st, pkt); +} + + +/* + * Compute duration of a packet, we do this by keeping a packet + * until the next one arrives, then we release it + */ +static void +pes_compute_duration(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt) +{ + th_pkt_t *next; + th_muxer_t *tm; + int delta; + + delta = abs(pkt->pkt_dts - pkt->pkt_pts); + + if(delta > 250000) + delta = 250000; + + if(delta > st->st_peak_presentation_delay) + st->st_peak_presentation_delay = delta; + + TAILQ_INSERT_TAIL(&st->st_durationq, pkt, pkt_queue_link); + + pkt = TAILQ_FIRST(&st->st_durationq); + if((next = TAILQ_NEXT(pkt, pkt_queue_link)) == NULL) + return; + + pkt->pkt_duration = next->pkt_dts - pkt->pkt_dts; + + TAILQ_REMOVE(&st->st_durationq, pkt, pkt_queue_link); + + if(pkt->pkt_duration < 1) { + pkt_deref(pkt); + return; + } + + pkt->pkt_stream = st; + + /* Alert all muxers tied to us that a new packet has arrived */ + + LIST_FOREACH(tm, &t->tht_muxers, tm_transport_link) + tm->tm_new_pkt(tm, st, pkt); + + /* Unref (and possibly free) the packet, muxers are supposed + to increase refcount or copy packet if they need anything */ + + pkt_deref(pkt); +} diff --git a/pes.h b/pes.h new file mode 100644 index 00000000..1f9bff96 --- /dev/null +++ b/pes.h @@ -0,0 +1,25 @@ +/* + * Elementary stream 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 . + */ + +#ifndef PES_H +#define PES_H + +int pes_packet_input(th_transport_t *th, th_stream_t *st, uint8_t *data, + size_t len); + +#endif /* PES_H */ diff --git a/psi.c b/psi.c index d965f4ff..fdefba36 100644 --- a/psi.c +++ b/psi.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -27,7 +28,7 @@ #include "psi.h" #include "transports.h" #include "dvb_support.h" -#include "ts.h" +#include "tsdemux.h" int psi_section_reassemble(psi_section_t *ps, uint8_t *data, int len, @@ -51,9 +52,9 @@ psi_section_reassemble(psi_section_t *ps, uint8_t *data, int len, if(ps->ps_offset < tsize) return -1; - if(chkcrc) { - /* XXX: Add CRC check */ - } + if(chkcrc && psi_crc32(ps->ps_data, tsize)) + return -1; + ps->ps_offset = tsize - (chkcrc ? 4 : 0); return 0; } @@ -73,7 +74,7 @@ psi_parse_pat(th_transport_t *t, uint8_t *ptr, int len, { uint16_t prognum; uint16_t pid; - th_pid_t *pi; + th_stream_t *st; if(len < 5) return -1; @@ -87,8 +88,8 @@ psi_parse_pat(th_transport_t *t, uint8_t *ptr, int len, pid = (ptr[2] & 0x1f) << 8 | ptr[3]; if(prognum != 0) { - pi = ts_add_pid(t, pid, HTSTV_TABLE); - pi->tp_got_section = pmt_callback; + st = transport_add_stream(t, pid, HTSTV_TABLE); + st->st_got_section = pmt_callback; } ptr += 4; @@ -98,6 +99,64 @@ psi_parse_pat(th_transport_t *t, uint8_t *ptr, int len, } +/** + * Append CRC + */ + +static int +psi_append_crc32(uint8_t *buf, int offset, int maxlen) +{ + uint32_t crc; + + if(offset + 4 > maxlen) + return -1; + + crc = psi_crc32(buf, offset); + + buf[offset + 0] = crc >> 24; + buf[offset + 1] = crc >> 16; + buf[offset + 2] = crc >> 8; + buf[offset + 3] = crc; + + assert(psi_crc32(buf, offset + 4) == 0); + + return offset + 4; +} + + +/** + * PAT generator + */ + +int +psi_build_pat(th_transport_t *t, uint8_t *buf, int maxlen) +{ + if(maxlen < 12) + return -1; + + buf[0] = 0; + buf[1] = 0xb0; /* reserved */ + buf[2] = 12 + 4 - 3; /* Length */ + + buf[3] = 0x00; /* transport stream id */ + buf[4] = 0x01; + + buf[5] = 0xc1; /* reserved + current_next_indicator + version */ + buf[6] = 0; + buf[7] = 0; + + buf[8] = 0; /* Program number, we only have one program */ + buf[9] = 1; + + buf[10] = 0; /* PMT pid */ + buf[11] = 100; + + return psi_append_crc32(buf, 12, maxlen); +} + + + + @@ -127,10 +186,11 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid) ptr += 9; len -= 9; - if(chksvcid && sid != t->tht_dvb_service_id) + if(chksvcid && sid != t->tht_dvb_service_id) { + printf("the service id is invalid\n"); return -1; - - while(dllen > 2) { + } + while(dllen > 1) { dtag = ptr[0]; dlen = ptr[1]; @@ -168,7 +228,7 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid) break; } - while(dllen > 2) { + while(dllen > 1) { dtag = ptr[0]; dlen = ptr[1]; @@ -194,7 +254,7 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid) } if(hts_stream_type != 0) - ts_add_pid(t, pid, hts_stream_type); + transport_add_stream(t, pid, hts_stream_type); } return 0; } @@ -203,19 +263,173 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid) +/** + * PAT generator + */ -const char * -htstvstreamtype2txt(tv_streamtype_t s) +int +psi_build_pmt(th_muxer_t *tm, uint8_t *buf0, int maxlen) { - 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"; - case HTSTV_TABLE: return "PSI table"; - default: return ""; + th_stream_t *st; + th_muxstream_t *tms; + int c, tlen, dlen, l; + uint8_t *buf, *buf1; + + + buf = buf0; + + if(maxlen < 12) + return -1; + + buf[0] = 2; /* table id, always 2 */ + + buf[3] = 0x00; /* program id */ + buf[4] = 0x01; + + buf[5] = 0xc1; /* current_next_indicator + version */ + buf[6] = 0; + buf[7] = 0; + + /* Find PID that carries PCR */ + + LIST_FOREACH(tms, &tm->tm_media_streams, tms_muxer_media_link) + if(tms->tms_dopcr) + break; + + if(tms == NULL) { + buf[8] = 0xff; + buf[9] = 0xff; + } else { + buf[8] = 0xe0 | (tms->tms_index >> 8); + buf[9] = tms->tms_index; } + + buf[10] = 0xf0; /* Program info length */ + buf[11] = 0x00; /* We dont have any such things atm */ + + buf += 12; + tlen = 12; + + LIST_FOREACH(tms, &tm->tm_media_streams, tms_muxer_media_link) { + st = tms->tms_stream; + + switch(st->st_type) { + case HTSTV_MPEG2VIDEO: + c = 0x02; + break; + + case HTSTV_MPEG2AUDIO: + c = 0x04; + break; + + case HTSTV_H264: + c = 0x1b; + break; + + case HTSTV_AC3: + c = 0x06; + break; + + default: + continue; + } + + + buf[0] = c; + buf[1] = 0xe0 | (tms->tms_index >> 8); + buf[2] = tms->tms_index; + + buf1 = &buf[3]; + tlen += 5; + buf += 5; + dlen = 0; + + switch(st->st_type) { + case HTSTV_AC3: + buf[0] = DVB_DESC_AC3; + buf[1] = 1; + buf[2] = 0; /* XXX: generate real AC3 desc */ + dlen = 3; + break; + default: + break; + } + + tlen += dlen; + buf += dlen; + + buf1[0] = 0xf0 | (dlen >> 8); + buf1[1] = dlen; + } + + l = tlen - 3 + 4; + + buf0[1] = 0xb0 | (l >> 8); + buf0[2] = l; + + return psi_append_crc32(buf0, tlen, maxlen); } + + + + +/* + * CRC32 + */ +static uint32_t crc_tab[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +uint32_t +psi_crc32(uint8_t *data, size_t datalen) +{ + uint32_t crc = 0xffffffff; + + while(datalen--) + crc = (crc << 8) ^ crc_tab[((crc >> 24) ^ *data++) & 0xff]; + + return crc; +} diff --git a/psi.h b/psi.h index 0fb7143d..20968cb4 100644 --- a/psi.h +++ b/psi.h @@ -35,4 +35,10 @@ int psi_parse_pat(th_transport_t *t, uint8_t *ptr, int len, int psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid); +uint32_t psi_crc32(uint8_t *data, size_t datalen); + +int psi_build_pat(th_transport_t *t, uint8_t *buf, int maxlen); + +int psi_build_pmt(th_muxer_t *tm, uint8_t *buf0, int maxlen); + #endif /* PSI_H_ */ diff --git a/pvr.c b/pvr.c index 17d7e40b..5df430b4 100644 --- a/pvr.c +++ b/pvr.c @@ -40,15 +40,21 @@ #include "subscriptions.h" #include "htsclient.h" #include "pvr.h" -#include "pvr_rec.h" #include "epg.h" #include "dispatch.h" +#include "buffer.h" struct pvr_rec_list pvrr_global_list; static void pvr_database_load(void); static void pvr_unrecord(pvr_rec_t *pvrr); static void pvrr_fsm(pvr_rec_t *pvrr); +static void pvrr_subscription_callback(struct th_subscription *s, + subscription_event_t event, + void *opaque); + +static void *pvr_recorder_thread(void *aux); +static void pvrr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt); /**************************************************************************** * @@ -131,9 +137,7 @@ pvr_inform_status_change(pvr_rec_t *pvrr) static void pvr_free(pvr_rec_t *pvrr) { - if(pvrr->pvrr_timer != NULL) - stimer_del(pvrr->pvrr_timer); - + dtimer_disarm(&pvrr->pvrr_timer); LIST_REMOVE(pvrr, pvrr_global_link); free(pvrr->pvrr_title); free(pvrr->pvrr_desc); @@ -431,48 +435,90 @@ pvr_database_load(void) fclose(fp); } +/* + * Replace any slash chars in a string with dash + */ +static void +deslashify(char *s) +{ + int i, len = strlen(s); + for(i = 0; i < len; i++) if(s[i] == '/') + s[i] = '-'; +} + /** - * wait for thread to exit + * Filename generator + * + * - convert from utf8 + * - avoid duplicate filenames + * */ - -static void -pvr_wait_thread(pvr_rec_t *pvrr) +static void +pvr_generate_filename(pvr_rec_t *pvrr) { - pvr_data_t *pd; - - if(pvrr->pvrr_rec_status == PVR_REC_STOP) - return; + char fullname[1000]; + char *x; + int tally = 0; + struct stat st; + char *name = pvrr->pvrr_title; - pvrr_set_rec_state(pvrr, PVR_REC_STOP); - - pd = malloc(sizeof(pvr_data_t)); - pd->tsb = NULL; - pthread_mutex_lock(&pvrr->pvrr_dq_mutex); - TAILQ_INSERT_TAIL(&pvrr->pvrr_dq, pd, link); - pthread_cond_signal(&pvrr->pvrr_dq_cond); - pthread_mutex_unlock(&pvrr->pvrr_dq_mutex); - pthread_join(pvrr->pvrr_ptid, NULL); + char *chname; + char *filename; + + + if(pvrr->pvrr_filename != NULL) { + free(pvrr->pvrr_filename); + pvrr->pvrr_filename = NULL; + } + + free(pvrr->pvrr_format); + pvrr->pvrr_format = strdup("matroska"); + + filename = utf8tofilename(name && name[0] ? name : "untitled"); + deslashify(filename); + + chname = utf8tofilename(pvrr->pvrr_channel->ch_name); + deslashify(chname); + + snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s", + config_get_str("pvrdir", "."), chname, filename, pvrr->pvrr_format); + + while(1) { + if(stat(fullname, &st) == -1) { + syslog(LOG_DEBUG, "pvr: File \"%s\" -- %s -- Using for recording", + fullname, strerror(errno)); + break; + } + + syslog(LOG_DEBUG, "pvr: Overwrite protection, file \"%s\" exists", + fullname); + + tally++; + snprintf(fullname, sizeof(fullname), "%s/%s-%s-%d.%s", + config_get_str("pvrdir", "."), chname, filename, tally, + pvrr->pvrr_format); + + } + + pvrr->pvrr_filename = strdup(fullname); + + if(pvrr->pvrr_printname != NULL) + free(pvrr->pvrr_printname); + + x = strrchr(pvrr->pvrr_filename, '/'); + pvrr->pvrr_printname = strdup(x ? x + 1 : pvrr->pvrr_filename); + + free(filename); + free(chname); } - -/** - * pvrr finite state machine - */ - - -static void pvr_record_callback(struct th_subscription *s, uint8_t *pkt, - th_pid_t *pi, int64_t pcr); - - static void -pvrr_fsm_timeout(void *aux) +pvrr_fsm_timeout(void *aux, int64_t now) { pvr_rec_t *pvrr = aux; - - pvrr->pvrr_timer = NULL; pvrr_fsm(pvrr); } @@ -484,6 +530,8 @@ pvrr_fsm(pvr_rec_t *pvrr) time_t delta; time_t now; + dtimer_disarm(&pvrr->pvrr_timer); + time(&now); switch(pvrr->pvrr_status) { @@ -492,10 +540,9 @@ pvrr_fsm(pvr_rec_t *pvrr) case HTSTV_PVR_STATUS_SCHEDULED: delta = pvrr->pvrr_start - 30 - now; - assert(pvrr->pvrr_timer == NULL); if(delta > 0) { - pvrr->pvrr_timer = stimer_add(pvrr_fsm_timeout, pvrr, delta); + dtimer_arm(&pvrr->pvrr_timer, pvrr_fsm_timeout, pvrr, delta); break; } @@ -512,34 +559,29 @@ pvrr_fsm(pvr_rec_t *pvrr) /* Add a timer that fires when recording ends */ - pvrr->pvrr_timer = stimer_add(pvrr_fsm_timeout, pvrr, delta); + dtimer_arm(&pvrr->pvrr_timer, pvrr_fsm_timeout, pvrr, delta); - TAILQ_INIT(&pvrr->pvrr_dq); - pthread_cond_init(&pvrr->pvrr_dq_cond, NULL); - pthread_mutex_init(&pvrr->pvrr_dq_mutex, NULL); - - pvrr->pvrr_s = subscription_create(pvrr->pvrr_channel, pvrr, - pvr_record_callback, 1000, "pvr"); + TAILQ_INIT(&pvrr->pvrr_pktq); + pthread_cond_init(&pvrr->pvrr_pktq_cond, NULL); + pthread_mutex_init(&pvrr->pvrr_pktq_mutex, NULL); pvrr->pvrr_status = HTSTV_PVR_STATUS_RECORDING; pvr_inform_status_change(pvrr); pvrr_set_rec_state(pvrr,PVR_REC_WAIT_SUBSCRIPTION); + + pvrr->pvrr_s = subscription_create(pvrr->pvrr_channel, 1000, "pvr", + pvrr_subscription_callback, + pvrr); break; - case HTSTV_PVR_STATUS_RECORDING: - /* recording completed */ + case HTSTV_PVR_STATUS_RECORDING: + /* recording completed (or aborted, or failed or somthing) */ pvrr->pvrr_status = pvrr->pvrr_error; pvr_inform_status_change(pvrr); pvr_database_save(); subscription_unsubscribe(pvrr->pvrr_s); - - pvr_wait_thread(pvrr); - - if(pvrr->pvrr_timer != NULL) { - stimer_del(pvrr->pvrr_timer); - pvrr->pvrr_timer = NULL; - } + dtimer_disarm(&pvrr->pvrr_timer); break; } } @@ -547,37 +589,37 @@ pvrr_fsm(pvr_rec_t *pvrr) /* - * PVR data input callback + * PVR new packet received */ - -static void -pvr_record_callback(struct th_subscription *s, uint8_t *pkt, th_pid_t *pi, - int64_t pcr) +static void +pvrr_packet_input(th_muxer_t *tm, th_stream_t *st, th_pkt_t *pkt) { - pvr_data_t *pd; - pvr_rec_t *pvrr = s->ths_opaque; - - if(pkt == NULL) + pvr_rec_t *pvrr = tm->tm_opaque; + + if(pvrr->pvrr_dts_offset == AV_NOPTS_VALUE) + pvrr->pvrr_dts_offset = pkt->pkt_dts; + + pkt = pkt_copy(pkt); + + pkt->pkt_dts -= pvrr->pvrr_dts_offset; + pkt->pkt_pts -= pvrr->pvrr_dts_offset; + + if(pkt->pkt_dts < 0 || pkt->pkt_pts < 0) { + pkt_deref(pkt); + printf("trashing negative dts/pts\n"); return; - - pd = malloc(sizeof(pvr_data_t)); - pd->tsb = malloc(188); - memcpy(pd->tsb, pkt, 188); - pd->pi = *pi; - pthread_mutex_lock(&pvrr->pvrr_dq_mutex); - TAILQ_INSERT_TAIL(&pvrr->pvrr_dq, pd, link); - pvrr->pvrr_dq_len++; - pthread_cond_signal(&pvrr->pvrr_dq_cond); - pthread_mutex_unlock(&pvrr->pvrr_dq_mutex); - - if(pvrr->pvrr_rec_status == PVR_REC_WAIT_SUBSCRIPTION) { - /* ok, first packet, start recording thread */ - pvrr_set_rec_state(pvrr, PVR_REC_WAIT_FOR_START); - pthread_create(&pvrr->pvrr_ptid, NULL, pvr_recorder_thread, pvrr); } + + pthread_mutex_lock(&pvrr->pvrr_pktq_mutex); + TAILQ_INSERT_TAIL(&pvrr->pvrr_pktq, pkt, pkt_queue_link); + pvrr->pvrr_pktq_len++; + pthread_cond_signal(&pvrr->pvrr_pktq_cond); + pthread_mutex_unlock(&pvrr->pvrr_pktq_mutex); } - +/* + * Internal recording state + */ void pvrr_set_rec_state(pvr_rec_t *pvrr, pvrr_rec_status_t status) { @@ -619,3 +661,457 @@ pvrr_set_rec_state(pvr_rec_t *pvrr, pvrr_rec_status_t status) pvrr->pvrr_rec_status = status; } +/* + * We've got a transport now, start recording + */ +static void +pvrr_transport_available(pvr_rec_t *pvrr, th_transport_t *t) +{ + th_muxer_t *tm = &pvrr->pvrr_muxer; + th_stream_t *st; + th_muxstream_t *tms; + AVFormatContext *fctx; + AVOutputFormat *fmt; + AVCodecContext *ctx; + AVCodec *codec; + enum CodecID codec_id; + enum CodecType codec_type; + const char *codec_name; + char urlname[500]; + int err; + + assert(pvrr->pvrr_rec_status == PVR_REC_WAIT_SUBSCRIPTION); + + tm->tm_opaque = pvrr; + tm->tm_new_pkt = pvrr_packet_input; + + pvr_generate_filename(pvrr); + + /* Find lavf format */ + + fmt = guess_format(pvrr->pvrr_format, NULL, NULL); + if(fmt == NULL) { + syslog(LOG_ERR, + "pvr: \"%s\" - Unable to open file format \".%s\" for output", + pvrr->pvrr_printname, pvrr->pvrr_format); + pvrr->pvrr_error = HTSTV_PVR_STATUS_FILE_ERROR; + pvrr_fsm(pvrr); + return; + } + + /* Init format context */ + + fctx = tm->tm_avfctx = av_alloc_format_context(); + + av_strlcpy(fctx->title, pvrr->pvrr_title ?: "", + sizeof(fctx->title)); + + av_strlcpy(fctx->comment, pvrr->pvrr_desc ?: "", + sizeof(tm->tm_avfctx->comment)); + + av_strlcpy(fctx->copyright, pvrr->pvrr_channel->ch_name, + sizeof(fctx->copyright)); + + + fctx->oformat = fmt; + + /* Open output file */ + + snprintf(urlname, sizeof(urlname), "file:%s", pvrr->pvrr_filename); + + if((err = url_fopen(&fctx->pb, urlname, URL_WRONLY)) < 0) { + syslog(LOG_ERR, + "pvr: \"%s\" - Unable to create output file \"%s\" -- %s\n", + pvrr->pvrr_printname, pvrr->pvrr_filename, + strerror(AVUNERROR(err))); + av_free(fctx); + tm->tm_avfctx = NULL; + pvrr->pvrr_error = HTSTV_PVR_STATUS_FILE_ERROR; + pvrr_fsm(pvrr); + return; + } + + + av_set_parameters(tm->tm_avfctx, NULL); + + LIST_FOREACH(st, &t->tht_streams, st_link) { + switch(st->st_type) { + default: + continue; + case HTSTV_MPEG2VIDEO: + codec_id = CODEC_ID_MPEG2VIDEO; + codec_type = CODEC_TYPE_VIDEO; + codec_name = "mpeg2 video"; + break; + + case HTSTV_MPEG2AUDIO: + codec_id = CODEC_ID_MP2; + codec_type = CODEC_TYPE_AUDIO; + codec_name = "mpeg2 audio"; + break; + + case HTSTV_AC3: + codec_id = CODEC_ID_AC3; + codec_type = CODEC_TYPE_AUDIO; + codec_name = "AC3 audio"; + break; + + case HTSTV_H264: + codec_id = CODEC_ID_H264; + codec_type = CODEC_TYPE_VIDEO; + codec_name = "h.264 video"; + break; + } + + codec = avcodec_find_decoder(codec_id); + if(codec == NULL) { + syslog(LOG_ERR, + "pvr: \"%s\" - Cannot find codec for %s, ignoring stream", + pvrr->pvrr_printname, codec_name); + continue; + } + + ctx = avcodec_alloc_context(); + ctx->codec_id = codec_id; + ctx->codec_type = codec_type; + + if(avcodec_open(ctx, codec) < 0) { + syslog(LOG_ERR, + "pvr: \"%s\" - Cannot open codec for %s, ignoring stream", + pvrr->pvrr_printname, codec_name); + free(ctx); + continue; + } + + tms = calloc(1, sizeof(th_muxstream_t)); + tms->tms_stream = st; + LIST_INSERT_HEAD(&tm->tm_media_streams, tms, tms_muxer_media_link); + + + tms->tms_avstream = av_mallocz(sizeof(AVStream)); + tms->tms_avstream->codec = ctx; + + tms->tms_index = fctx->nb_streams; + tm->tm_avfctx->streams[fctx->nb_streams] = tms->tms_avstream; + fctx->nb_streams++; + } + + /* Fire up recorder thread */ + + pvrr_set_rec_state(pvrr, PVR_REC_WAIT_FOR_START); + pthread_create(&pvrr->pvrr_ptid, NULL, pvr_recorder_thread, pvrr); + LIST_INSERT_HEAD(&t->tht_muxers, tm, tm_transport_link); +} + + +/* + * We've lost our transport, stop recording + */ + +static void +pvrr_transport_unavailable(pvr_rec_t *pvrr, th_transport_t *t) +{ + th_muxer_t *tm = &pvrr->pvrr_muxer; + th_muxstream_t *tms; + th_pkt_t *pkt; + AVFormatContext *fctx = tm->tm_avfctx; + AVStream *avst; + int i; + + LIST_REMOVE(tm, tm_transport_link); + + pvrr->pvrr_dts_offset = AV_NOPTS_VALUE; + + pvrr_set_rec_state(pvrr, PVR_REC_STOP); + pthread_cond_signal(&pvrr->pvrr_pktq_cond); + pthread_join(pvrr->pvrr_ptid, NULL); + + if(fctx != NULL) { + + /* Write trailer if we've written anything at all */ + + if(pvrr->pvrr_header_written) + av_write_trailer(tm->tm_avfctx); + + /* Close streams and format */ + + for(i = 0; i < fctx->nb_streams; i++) { + avst = fctx->streams[i]; + avcodec_close(avst->codec); + free(avst->codec); + free(avst); + } + + url_fclose(&fctx->pb); + free(fctx); + } + + /* Remove any pending packet for queue */ + + while((pkt = TAILQ_FIRST(&pvrr->pvrr_pktq)) != NULL) + pkt_deref(pkt); + + /* Destroy muxstreams */ + + while((tms = LIST_FIRST(&tm->tm_media_streams)) != NULL) { + LIST_REMOVE(tms, tms_muxer_media_link); + free(tms); + } + +} + + + +/* + * We get a callback here when the subscription status is updated, + * ie, when we are attached to a transport and when we are detached + */ +static void +pvrr_subscription_callback(struct th_subscription *s, + subscription_event_t event, void *opaque) +{ + th_transport_t *t = s->ths_transport; + pvr_rec_t *pvrr = opaque; + + switch(event) { + case TRANSPORT_AVAILABLE: + pvrr_transport_available(pvrr, t); + break; + + case TRANSPORT_UNAVAILABLE: + pvrr_transport_unavailable(pvrr, t); + break; + } +} + +/* + * Recorder thread + */ +static void * +pvr_recorder_thread(void *aux) +{ + th_pkt_t *pkt; + pvr_rec_t *pvrr = aux; + char *t, txt2[50]; + int run = 1; + time_t now; + + ctime_r(&pvrr->pvrr_stop, txt2); + t = strchr(txt2, '\n'); + if(t != NULL) + *t = 0; + + syslog(LOG_INFO, "pvr: \"%s\" - Recording started, ends at %s", + pvrr->pvrr_printname, txt2); + + + pthread_mutex_lock(&pvrr->pvrr_pktq_mutex); + + while(run) { + switch(pvrr->pvrr_rec_status) { + case PVR_REC_WAIT_FOR_START: + + time(&now); + if(now >= pvrr->pvrr_start) + pvrr->pvrr_rec_status = PVR_REC_WAIT_AUDIO_LOCK; + break; + + case PVR_REC_WAIT_AUDIO_LOCK: + case PVR_REC_WAIT_VIDEO_LOCK: + case PVR_REC_RUNNING: + case PVR_REC_COMMERCIAL: + break; + + default: + run = 0; + continue; + } + + if(pvrr->pvrr_stop < now) + break; + + if((pkt = TAILQ_FIRST(&pvrr->pvrr_pktq)) == NULL) { + pthread_cond_wait(&pvrr->pvrr_pktq_cond, &pvrr->pvrr_pktq_mutex); + continue; + } + + TAILQ_REMOVE(&pvrr->pvrr_pktq, pkt, pkt_queue_link); + pvrr->pvrr_pktq_len--; + pthread_mutex_unlock(&pvrr->pvrr_pktq_mutex); + + pvrr_record_packet(pvrr, pkt); + pkt_deref(pkt); + + pthread_mutex_lock(&pvrr->pvrr_pktq_mutex); + } + pthread_mutex_unlock(&pvrr->pvrr_pktq_mutex); + + syslog(LOG_INFO, "pvr: \"%s\" - Recording completed", + pvrr->pvrr_printname); + + return NULL; +} + + + +/** + * Check if all streams of the given type has been decoded + */ +static int +is_all_decoded(th_muxer_t *tm, enum CodecType type) +{ + th_muxstream_t *tms; + AVStream *st; + + LIST_FOREACH(tms, &tm->tm_media_streams, tms_muxer_media_link) { + st = tms->tms_avstream; + if(st->codec->codec->type == type && tms->tms_decoded == 0) + return 0; + } + return 1; +} + + + +/** + * Write a packet to output file + */ +static void +pvrr_record_packet(pvr_rec_t *pvrr, th_pkt_t *pkt) +{ + th_muxer_t *tm = &pvrr->pvrr_muxer; + AVFormatContext *fctx = tm->tm_avfctx; + th_muxstream_t *tms; + AVStream *st, *stx; + AVCodecContext *ctx; + AVPacket avpkt; + void *abuf; + AVFrame pic; + int r, data_size, i; + void *buf; + char txt[100]; + size_t bufsize; + + LIST_FOREACH(tms, &tm->tm_media_streams, tms_muxer_media_link) + if(tms->tms_stream == pkt->pkt_stream) + break; + + if(tms == NULL) + return; + + st = tms->tms_avstream; + ctx = st->codec; + + /* Make sure packet is in memory */ + + buf = pkt_payload(pkt); + bufsize = pkt_len(pkt); + + switch(pvrr->pvrr_rec_status) { + default: + break; + + case PVR_REC_WAIT_AUDIO_LOCK: + if(ctx->codec_type != CODEC_TYPE_AUDIO || tms->tms_decoded) + break; + + abuf = malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); + r = avcodec_decode_audio(ctx, abuf, &data_size, buf, bufsize); + free(abuf); + + if(r != 0 && data_size) { + syslog(LOG_DEBUG, "pvr: \"%s\" - " + "Stream #%d: \"%s\" decoded a complete audio frame: " + "%d channels in %d Hz\n", + pvrr->pvrr_printname, tms->tms_index, + st->codec->codec->name, + st->codec->channels, + st->codec->sample_rate); + + tms->tms_decoded = 1; + } + + if(is_all_decoded(tm, CODEC_TYPE_AUDIO)) + pvrr_set_rec_state(pvrr, PVR_REC_WAIT_VIDEO_LOCK); + break; + + case PVR_REC_WAIT_VIDEO_LOCK: + if(ctx->codec_type != CODEC_TYPE_VIDEO || tms->tms_decoded) + break; + + r = avcodec_decode_video(st->codec, &pic, &data_size, buf, bufsize); + if(r != 0 && data_size) { + syslog(LOG_DEBUG, "pvr: \"%s\" - " + "Stream #%d: \"%s\" decoded a complete video frame: " + "%d x %d at %.2fHz\n", + pvrr->pvrr_printname, tms->tms_index, + ctx->codec->name, + ctx->width, st->codec->height, + (float)ctx->time_base.den / (float)ctx->time_base.num); + + tms->tms_decoded = 1; + } + + if(!is_all_decoded(tm, CODEC_TYPE_VIDEO)) + break; + + /* All Audio & Video decoded, start recording */ + + pvrr_set_rec_state(pvrr, PVR_REC_RUNNING); + + if(!pvrr->pvrr_header_written) { + pvrr->pvrr_header_written = 1; + + if(av_write_header(fctx)) + break; + + syslog(LOG_DEBUG, + "pvr: \"%s\" - Header written to file, stream dump:", + pvrr->pvrr_printname); + + for(i = 0; i < fctx->nb_streams; i++) { + stx = fctx->streams[i]; + + avcodec_string(txt, sizeof(txt), stx->codec, 1); + + syslog(LOG_DEBUG, "pvr: \"%s\" - Stream #%d: %s [%d/%d]", + pvrr->pvrr_printname, i, txt, + stx->time_base.num, stx->time_base.den); + + } + } + /* FALLTHRU */ + + case PVR_REC_RUNNING: + + if(pkt->pkt_commercial == COMMERCIAL_YES) { + pvrr_set_rec_state(pvrr, PVR_REC_COMMERCIAL); + break; + } + + av_init_packet(&avpkt); + avpkt.stream_index = tms->tms_index; + + avpkt.dts = av_rescale_q(pkt->pkt_dts, AV_TIME_BASE_Q, st->time_base); + avpkt.pts = av_rescale_q(pkt->pkt_pts, AV_TIME_BASE_Q, st->time_base); + avpkt.data = buf; + avpkt.size = bufsize; + avpkt.duration = pkt->pkt_duration; + + r = av_interleaved_write_frame(fctx, &avpkt); + break; + + + case PVR_REC_COMMERCIAL: + + if(pkt->pkt_commercial != COMMERCIAL_YES) { + + LIST_FOREACH(tms, &tm->tm_media_streams, tms_muxer_media_link) + tms->tms_decoded = 0; + + pvrr_set_rec_state(pvrr, PVR_REC_WAIT_AUDIO_LOCK); + } + break; + } +} + diff --git a/pvr.h b/pvr.h index 2aece0d3..3d6905b4 100644 --- a/pvr.h +++ b/pvr.h @@ -19,9 +19,76 @@ #ifndef PVR_H #define PVR_H +#include + extern char *pvrpath; extern struct pvr_rec_list pvrr_global_list; + +/* + * PVR Internal recording status + */ +typedef enum { + PVR_REC_STOP, + PVR_REC_WAIT_SUBSCRIPTION, + PVR_REC_WAIT_FOR_START, + PVR_REC_WAIT_AUDIO_LOCK, + PVR_REC_WAIT_VIDEO_LOCK, + PVR_REC_RUNNING, + PVR_REC_COMMERCIAL, + +} pvrr_rec_status_t; + + +/* + * PVR recording session + */ +typedef struct pvr_rec { + + LIST_ENTRY(pvr_rec) pvrr_global_link; + + th_channel_t *pvrr_channel; + + time_t pvrr_start; + time_t pvrr_stop; + + char *pvrr_filename; /* May be null if we havent figured out a name + yet, this happens upon record start. + Notice that this is full path */ + char *pvrr_title; /* Title in UTF-8 */ + char *pvrr_desc; /* Description in UTF-8 */ + + char *pvrr_printname; /* Only ASCII chars, used for logging and such */ + char *pvrr_format; /* File format trailer */ + + char pvrr_status; /* defined in libhts/htstv.h */ + char pvrr_error; /* dito - but status returned from recorder */ + + pvrr_rec_status_t pvrr_rec_status; /* internal recording status */ + + struct th_pkt_queue pvrr_pktq; + int pvrr_pktq_len; + pthread_mutex_t pvrr_pktq_mutex; + pthread_cond_t pvrr_pktq_cond; + + int pvrr_ref; + + th_subscription_t *pvrr_s; + + pthread_t pvrr_ptid; + dtimer_t pvrr_timer; + + th_muxer_t pvrr_muxer; + + int pvrr_header_written; + + int64_t pvrr_dts_offset; + +} pvr_rec_t; + + + + typedef enum { RECOP_TOGGLE, RECOP_ONCE, diff --git a/pvr_rec.c b/pvr_rec.c deleted file mode 100644 index 3005b783..00000000 --- a/pvr_rec.c +++ /dev/null @@ -1,857 +0,0 @@ -/* - * Private Video Recorder, Recording 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 - -#include - -#include "tvhead.h" -#include "channels.h" -#include "transports.h" -#include "pvr.h" -#include "epg.h" - - -/* - * - */ - -LIST_HEAD(ts_pid_head, ts_pid); - -typedef struct ts_pid { - - LIST_ENTRY(ts_pid) tsp_link; - - int tsp_pid; - int tsp_cc; - int tsp_cc_errors; - - int tsp_pus; - - uint8_t *tsp_buf; - size_t tsp_bufptr; - size_t tsp_bufsize; - -} ts_pid_t; - - -static int pvr_proc_tsb(pvr_rec_t *pvrr, struct ts_pid_head *pidlist, - pvr_data_t *pd, th_subscription_t *s); - -static void *pwo_init(th_subscription_t *s, pvr_rec_t *pvrr); - -static int pwo_writepkt(pvr_rec_t *pvrr, th_subscription_t *s, - uint32_t startcode, int streamidx, uint8_t *buf, - size_t len, tv_streamtype_t type); - - -static int pwo_end(pvr_rec_t *pvrr); - -static void pvr_generate_filename(pvr_rec_t *pvrr); - - -/* - * Recording thread - */ - -void * -pvr_recorder_thread(void *aux) -{ - pvr_rec_t *pvrr = aux; - pvr_data_t *pd; - char *t, txt2[50]; - int x, run = 1; - struct ts_pid_head pids; - ts_pid_t *tsp; - th_subscription_t *s = pvrr->pvrr_s; - void *opaque; - time_t now; - - pvr_generate_filename(pvrr); - - - opaque = pwo_init(s, pvrr); - - if(opaque == NULL) { - pvrr->pvrr_error = HTSTV_PVR_STATUS_FILE_ERROR; - return NULL; - } - - ctime_r(&pvrr->pvrr_stop, txt2); - t = strchr(txt2, '\n'); - if(t != NULL) - *t = 0; - - syslog(LOG_INFO, "pvr: \"%s\" - Recording started, ends at %s", - pvrr->pvrr_printname, txt2); - - - LIST_INIT(&pids); - - while(run) { - - switch(pvrr->pvrr_rec_status) { - case PVR_REC_WAIT_FOR_START: - - time(&now); - if(now >= pvrr->pvrr_start) - pvrr->pvrr_rec_status = PVR_REC_WAIT_AUDIO_LOCK; - break; - - case PVR_REC_WAIT_AUDIO_LOCK: - case PVR_REC_WAIT_VIDEO_LOCK: - case PVR_REC_RUNNING: - case PVR_REC_COMMERCIAL: - break; - - default: - run = 0; - continue; - } - - if(pvrr->pvrr_stop < now) { - syslog(LOG_INFO, "pvr: \"%s\" - Recording completed", - pvrr->pvrr_printname); - break; - } - - pthread_mutex_lock(&pvrr->pvrr_dq_mutex); - - while((pd = TAILQ_FIRST(&pvrr->pvrr_dq)) == NULL) - pthread_cond_wait(&pvrr->pvrr_dq_cond, &pvrr->pvrr_dq_mutex); - - TAILQ_REMOVE(&pvrr->pvrr_dq, pd, link); - pvrr->pvrr_dq_len--; - pthread_mutex_unlock(&pvrr->pvrr_dq_mutex); - - if(pvrr->pvrr_dq_len * 188 > 20000000) { - /* Buffer exceeding 20Mbyte, target media is too slow, - bail out */ - syslog(LOG_INFO, "pvr: \"%s\" - Disk i/o too slow, aborting", - pvrr->pvrr_printname); - - pvrr->pvrr_error = HTSTV_PVR_STATUS_BUFFER_ERROR; - break; - } - - if(pd->tsb == NULL) { - run = 0; - } else { - - x = pvr_proc_tsb(pvrr, &pids, pd, s); - free(pd->tsb); - - if(x != 0) { - - switch(errno) { - case ENOSPC: - pvrr->pvrr_error = HTSTV_PVR_STATUS_DISK_FULL; - syslog(LOG_INFO, "pvr: \"%s\" - Disk full, aborting", - pvrr->pvrr_printname); - break; - default: - pvrr->pvrr_error = HTSTV_PVR_STATUS_FILE_ERROR; - syslog(LOG_INFO, "pvr: \"%s\" - File error, aborting", - pvrr->pvrr_printname); - break; - } - } - } - free(pd); - } - - pwo_end(pvrr); - - while((tsp = LIST_FIRST(&pids)) != NULL) { - LIST_REMOVE(tsp, tsp_link); - free(tsp->tsp_buf); - free(tsp); - } - - return NULL; -} - - - - - -/** - * Filename generator - * - * - convert from utf8 - * - avoid duplicate filenames - * - */ - -static void -deslashify(char *s) -{ - int i, len = strlen(s); - for(i = 0; i < len; i++) if(s[i] == '/') - s[i] = '-'; -} - -static void -pvr_generate_filename(pvr_rec_t *pvrr) -{ - char fullname[1000]; - char *x; - int tally = 0; - struct stat st; - char *name = pvrr->pvrr_title; - - char *chname; - char *filename; - - - if(pvrr->pvrr_filename != NULL) { - free(pvrr->pvrr_filename); - pvrr->pvrr_filename = NULL; - } - - free(pvrr->pvrr_format); - pvrr->pvrr_format = strdup("nut"); - - filename = utf8tofilename(name && name[0] ? name : "untitled"); - deslashify(filename); - - chname = utf8tofilename(pvrr->pvrr_channel->ch_name); - deslashify(chname); - - snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s", - config_get_str("pvrdir", "."), chname, filename, pvrr->pvrr_format); - - while(1) { - if(stat(fullname, &st) == -1) { - syslog(LOG_DEBUG, "pvr: File \"%s\" -- %s -- Using for recording", - fullname, strerror(errno)); - break; - } - - syslog(LOG_DEBUG, "pvr: Overwrite protection, file \"%s\" exists", - fullname); - - tally++; - snprintf(fullname, sizeof(fullname), "%s/%s-%s-%d.%s", - config_get_str("pvrdir", "."), chname, filename, tally, - pvrr->pvrr_format); - - } - - pvrr->pvrr_filename = strdup(fullname); - - if(pvrr->pvrr_printname != NULL) - free(pvrr->pvrr_printname); - - x = strrchr(pvrr->pvrr_filename, '/'); - pvrr->pvrr_printname = strdup(x ? x + 1 : pvrr->pvrr_filename); - - free(filename); - free(chname); -} - - - -/* - * Transport stream parser and recording functions - * - */ - - - -#define getu32(b, l) ({ \ - uint32_t x = (b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]); \ - b+=4; \ - l-=4; \ - x; \ -}) - -#define getu16(b, l) ({ \ - uint16_t x = (b[0] << 8 | b[1]); \ - b+=2; \ - l-=2; \ - x; \ -}) - -#define getu8(b, l) ({ \ - uint8_t x = b[0]; \ - b+=1; \ - l-=1; \ - x; \ -}) - -#define getpts(b, l) ({ \ - int64_t _pts; \ - _pts = (int64_t)((getu8(b, l) >> 1) & 0x07) << 30; \ - _pts |= (int64_t)(getu16(b, l) >> 1) << 15; \ - _pts |= (int64_t)(getu16(b, l) >> 1); \ - _pts; \ -}) - - -static int -pvr_proc_tsb(pvr_rec_t *pvrr, struct ts_pid_head *pidlist, pvr_data_t *pd, - th_subscription_t *s) -{ - int pid, adaptation_field_control; - int len; - uint8_t *payload, *tsb; - ts_pid_t *tsp; - - tsb = pd->tsb; - - pid = (tsb[1] & 0x1f) << 8 | tsb[2]; - - LIST_FOREACH(tsp, pidlist, tsp_link) - if(tsp->tsp_pid == pid) - break; - - if(tsp == NULL) { - tsp = calloc(1, sizeof(ts_pid_t)); - LIST_INSERT_HEAD(pidlist, tsp, tsp_link); - tsp->tsp_pid = pid; - } - - adaptation_field_control = (tsb[3] >> 4) & 0x03; - - if(adaptation_field_control & 0x01) { - - if(adaptation_field_control) { - uint32_t adaptation_field_length = 0; - - if(adaptation_field_control == 3) - adaptation_field_length = 1 + tsb[4]; - - payload = tsb + adaptation_field_length + 4; - - len = 188 - 4 - adaptation_field_length; - - if(len < 0) - return 0; - - if(tsb[1] & 0x40) { - - if(tsp->tsp_bufptr > 6) { - - uint8_t *b = tsp->tsp_buf; - size_t l = tsp->tsp_bufptr; - uint32_t sc; - - sc = getu32(b, l); - getu16(b, l); /* Skip len */ - - if(pwo_writepkt(pvrr, s, sc, pd->pi.tp_index, b, l, pd->pi.tp_type)) - return 1; - } - tsp->tsp_bufptr = 0; - tsp->tsp_pus = 1; - } - - if(tsp->tsp_pus == 1) { - - if(tsp->tsp_bufptr + len >= tsp->tsp_bufsize) { - tsp->tsp_bufsize += len * 3; - tsp->tsp_buf = realloc(tsp->tsp_buf, tsp->tsp_bufsize); - } - - memcpy(tsp->tsp_buf + tsp->tsp_bufptr, payload, len); - tsp->tsp_bufptr += len; - - } - } - } - return 0; -} - - - - - -/****************************************************************************** - * - * ffmpeg based writeout - * - */ - - -#define PWO_FFMPEG_MAXPIDS 16 - - -typedef struct pwo_ffmpeg { - - AVOutputFormat *fmt; - AVFormatContext *fctx; - - struct { - int streamid; - int decoded; - enum CodecType codec_type; - int64_t next_dts; - int64_t next_pts; - int64_t last_dts; - int64_t duration; - - - } pids[PWO_FFMPEG_MAXPIDS]; - - int64_t ref_clock; - - int hdr_written; - int audio_pids; - int video_pids; - - int prologue; - int header_written; - -} pwo_ffmpeg_t; - - -static void * -pwo_init(th_subscription_t *s, pvr_rec_t *pvrr) -{ - char urlname[400]; - int i, err; - th_transport_t *t = s->ths_transport; - pwo_ffmpeg_t *pf; - th_pid_t *p; - AVStream *st; - AVCodec *codec; - const char *cname; - - pf = calloc(1, sizeof(pwo_ffmpeg_t)); - - pf->fmt = guess_format(pvrr->pvrr_format, NULL, NULL); - if(pf->fmt == NULL) { - syslog(LOG_ERR, - "pvr: \"%s\" - Unable to open file format \".%s\" for output", - pvrr->pvrr_printname, pvrr->pvrr_format); - free(pf); - return NULL; - } - - pf->ref_clock = AV_NOPTS_VALUE; - pf->fctx = av_alloc_format_context(); - - av_strlcpy(pf->fctx->title, pvrr->pvrr_title ?: "", - sizeof(pf->fctx->title)); - - av_strlcpy(pf->fctx->comment, pvrr->pvrr_desc ?: "", - sizeof(pf->fctx->comment)); - - av_strlcpy(pf->fctx->copyright, pvrr->pvrr_channel->ch_name, - sizeof(pf->fctx->copyright)); - - pf->fctx->oformat = pf->fmt; - - snprintf(urlname, sizeof(urlname), "file:%s", pvrr->pvrr_filename); - - if((err = url_fopen(&pf->fctx->pb, urlname, URL_WRONLY)) < 0) { - syslog(LOG_ERR, - "pvr: \"%s\" - Unable to create output file \"%s\" -- %s\n", - pvrr->pvrr_printname, pvrr->pvrr_filename, - strerror(AVUNERROR(err))); - av_free(pf->fctx); - free(pf); - return NULL; - } - - av_set_parameters(pf->fctx, NULL); /* Fix NULL -stuff */ - - LIST_FOREACH(p, &t->tht_pids, tp_link) { - i = p->tp_index; - - switch(p->tp_type) { - case HTSTV_MPEG2VIDEO: - break; - case HTSTV_MPEG2AUDIO: - break; - case HTSTV_H264: - break; - case HTSTV_AC3: - break; - default: - pf->pids[i].streamid = -1; - continue; - } - - st = av_mallocz(sizeof(AVStream)); - pf->fctx->streams[pf->fctx->nb_streams] = st; - - st->codec = avcodec_alloc_context(); - - switch(p->tp_type) { - default: - continue; - case HTSTV_MPEG2VIDEO: - st->codec->codec_id = CODEC_ID_MPEG2VIDEO; - st->codec->codec_type = CODEC_TYPE_VIDEO; - cname = "mpeg2 video"; - pf->video_pids++; - break; - - case HTSTV_MPEG2AUDIO: - st->codec->codec_id = CODEC_ID_MP2; - st->codec->codec_type = CODEC_TYPE_AUDIO; - cname = "mpeg2 audio"; - pf->audio_pids++; - break; - - case HTSTV_AC3: - st->codec->codec_id = CODEC_ID_AC3; - st->codec->codec_type = CODEC_TYPE_AUDIO; - cname = "ac3 audio"; - pf->audio_pids++; - break; - - case HTSTV_H264: - st->codec->codec_id = CODEC_ID_H264; - st->codec->codec_type = CODEC_TYPE_VIDEO; - cname = "h.264 video"; - pf->video_pids++; - break; - } - - codec = avcodec_find_decoder(st->codec->codec_id); - if(codec == NULL) { - syslog(LOG_ERR, "pvr: \"%s\" - " - "Cannot find codec for %s, ignoring stream", - pvrr->pvrr_printname, cname); - continue; - } - - if(avcodec_open(st->codec, codec) < 0) { - syslog(LOG_ERR, "pvr: \"%s\" - " - "Cannot open codec for %s, ignoring stream", - pvrr->pvrr_printname, cname); - continue; - } - - st->parser = av_parser_init(st->codec->codec_id); - pf->pids[i].codec_type = st->codec->codec_type; - pf->pids[i].streamid = pf->fctx->nb_streams; - pf->pids[i].next_dts = AV_NOPTS_VALUE; - pf->pids[i].next_pts = AV_NOPTS_VALUE; - pf->pids[i].last_dts = AV_NOPTS_VALUE; - pf->pids[i].duration = 0; - - pf->fctx->nb_streams++; - } - pvrr->pvrr_opaque = pf; - return pf; -} - - - -static int -pwo_writepkt(pvr_rec_t *pvrr, th_subscription_t *s, uint32_t startcode, - int pidindex, uint8_t *buf, size_t len, - tv_streamtype_t type) -{ - pwo_ffmpeg_t *pf = pvrr->pvrr_opaque; - th_transport_t *th = s->ths_transport; - uint8_t flags, hlen, x; - int64_t dts = AV_NOPTS_VALUE, pts = AV_NOPTS_VALUE; - int r, rlen, i, g, j; - int lavf_index = pf->pids[pidindex].streamid; - AVStream *st, *stx; - AVPacket pkt; - uint8_t *pbuf; - int pbuflen, data_size, duration; - char txt[100]; - void *abuf; - AVFrame pic; - - AVRational mpeg_tc = {1, 90000}; - - if(lavf_index == -1) - return 0; - - x = getu8(buf, len); - flags = getu8(buf, len); - hlen = getu8(buf, len); - - if(len < hlen) - return 0; - - if((x & 0xc0) != 0x80) - return 0; - - if((flags & 0xc0) == 0xc0) { - if(hlen < 10) - return 0; - - pts = getpts(buf, len); - dts = getpts(buf, len); - - hlen -= 10; - - } else if((flags & 0xc0) == 0x80) { - if(hlen < 5) - return 0; - - dts = pts = getpts(buf, len); - hlen -= 5; - } - - buf += hlen; - len -= hlen; - - st = pf->fctx->streams[lavf_index]; - - if(dts != AV_NOPTS_VALUE && pf->ref_clock == AV_NOPTS_VALUE) - pf->ref_clock = dts; - - if(dts == AV_NOPTS_VALUE) - dts = pf->pids[pidindex].next_dts; - if(dts != AV_NOPTS_VALUE) { - dts -= pf->ref_clock; - if(dts < 0 && pf->prologue > 0) - dts = AV_NOPTS_VALUE; - else - dts &= 0x1ffffffffULL; - } - if(dts != AV_NOPTS_VALUE) - dts = av_rescale_q(dts, mpeg_tc, AV_TIME_BASE_Q); - - - if(pts == AV_NOPTS_VALUE) - pts = pf->pids[pidindex].next_pts; - if(pts != AV_NOPTS_VALUE) { - pts -= pf->ref_clock; - if(pts < 0 && pf->prologue > 0) - pts = AV_NOPTS_VALUE; - else - pts &= 0x1ffffffffULL; - } - if(pts != AV_NOPTS_VALUE) - pts = av_rescale_q(pts, mpeg_tc, AV_TIME_BASE_Q); - - - while(1) { - rlen = av_parser_parse(st->parser, st->codec, &pbuf, &pbuflen, - buf, len, pts, dts); - if(pbuflen == 0) - return 0; - - switch(pvrr->pvrr_rec_status) { - default: - break; - - - case PVR_REC_WAIT_AUDIO_LOCK: - if(st->codec->codec_type != CODEC_TYPE_AUDIO || - pf->pids[pidindex].decoded) - break; - - abuf = malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); - r = avcodec_decode_audio(st->codec, abuf, &data_size, pbuf, pbuflen); - free(abuf); - - if(r != 0 && data_size) { - syslog(LOG_DEBUG, "pvr: \"%s\" - " - "Stream #%d: \"%s\" decoded a complete audio frame: " - "%d channels in %d Hz\n", - pvrr->pvrr_printname, lavf_index, - st->codec->codec->name, - st->codec->channels, - st->codec->sample_rate); - - pf->pids[pidindex].decoded = 1; - } - - j = 0; - for(i = 0; i < PWO_FFMPEG_MAXPIDS; i++) { - if(pf->pids[i].codec_type == CODEC_TYPE_AUDIO && pf->pids[i].decoded) - j++; - } - - if(j == pf->audio_pids) - pvrr_set_rec_state(pvrr, PVR_REC_WAIT_VIDEO_LOCK); - break; - - case PVR_REC_WAIT_VIDEO_LOCK: - if(st->codec->codec_type != CODEC_TYPE_VIDEO || - pf->pids[pidindex].decoded) - break; - - r = avcodec_decode_video(st->codec, &pic, &data_size, pbuf, pbuflen); - if(r != 0 && data_size) { - syslog(LOG_DEBUG, "pvr: \"%s\" - " - "Stream #%d: \"%s\" decoded a complete video frame: " - "%d x %d at %.2fHz\n", - pvrr->pvrr_printname, lavf_index, - st->codec->codec->name, - st->codec->width, st->codec->height, - (float)st->codec->time_base.den / - (float)st->codec->time_base.num); - - pf->pids[pidindex].decoded = 1; - } - - j = 0; - for(i = 0; i < PWO_FFMPEG_MAXPIDS; i++) { - if(pf->pids[i].codec_type == CODEC_TYPE_VIDEO && pf->pids[i].decoded) - j++; - } - - if(j != pf->video_pids) - break; - - pvrr_set_rec_state(pvrr, PVR_REC_RUNNING); - - if(!pf->header_written) { - pf->header_written = 1; - - if(av_write_header(pf->fctx)) - return 0; - - syslog(LOG_DEBUG, - "pvr: \"%s\" - Header written to file, stream dump:", - pvrr->pvrr_printname); - - for(i = 0; i < pf->fctx->nb_streams; i++) { - stx = pf->fctx->streams[i]; - g = ff_gcd(stx->time_base.num, stx->time_base.den); - - avcodec_string(txt, sizeof(txt), stx->codec, 1); - - syslog(LOG_DEBUG, "pvr: \"%s\" - Stream #%d: %s [%d/%d]", - pvrr->pvrr_printname, i, txt, - stx->time_base.num, stx->time_base.den); - - } - } - /* FALLTHRU */ - - case PVR_REC_RUNNING: - - if(th != NULL && th->tht_tt_commercial_advice == COMMERCIAL_YES) { - pvrr_set_rec_state(pvrr, PVR_REC_COMMERCIAL); - break; - } - - if(dts == AV_NOPTS_VALUE || pts == AV_NOPTS_VALUE) - break; - - switch(st->codec->codec_type) { - default: - duration = 0; - break; - - case CODEC_TYPE_VIDEO: - duration = 1000000 * - st->codec->time_base.num / st->codec->time_base.den; - break; - - case CODEC_TYPE_AUDIO: - duration = 1000000 * st->codec->frame_size / st->codec->sample_rate; - } - - av_init_packet(&pkt); - pkt.stream_index = lavf_index; - - pkt.dts = av_rescale_q(dts, AV_TIME_BASE_Q, st->time_base); - pkt.pts = av_rescale_q(pts, AV_TIME_BASE_Q, st->time_base); - - if(pkt.dts <= pf->pids[pidindex].last_dts) { - if(pkt.pts == pkt.dts) - pkt.pts = pf->pids[pidindex].last_dts + 1; - pkt.dts = pf->pids[pidindex].last_dts + 1; - } - - pf->pids[pidindex].last_dts = pkt.dts; - - pkt.data = pbuf; - pkt.size = pbuflen; - pkt.duration = duration; - - if(st->codec->codec_type == CODEC_TYPE_AUDIO || - st->parser->pict_type == FF_I_TYPE) - pkt.flags |= PKT_FLAG_KEY; - r = av_interleaved_write_frame(pf->fctx, &pkt); - - dts += duration; - pts += duration; - pf->pids[pidindex].next_dts = dts; - pf->pids[pidindex].next_pts = pts; - break; - - - case PVR_REC_COMMERCIAL: - if(th == NULL || th->tht_tt_commercial_advice != COMMERCIAL_YES) { - - for(i = 0; i < PWO_FFMPEG_MAXPIDS; i++) - pf->pids[i].decoded = 0; - - pvrr_set_rec_state(pvrr, PVR_REC_WAIT_AUDIO_LOCK); - } - break; - } - buf += rlen; - len -= rlen; - } - return 0; -} - - - -static int -pwo_end(pvr_rec_t *pvrr) -{ - pwo_ffmpeg_t *pf = pvrr->pvrr_opaque; - AVStream *st; - int i; - - if(pf->header_written) - av_write_trailer(pf->fctx); - - for(i = 0; i < pf->fctx->nb_streams; i++) { - st = pf->fctx->streams[i]; - avcodec_close(st->codec); - free(st->codec); - free(st); - } - - url_fclose(&pf->fctx->pb); - free(pf->fctx); - - if(!pf->header_written) { - syslog(LOG_DEBUG, "pvr: \"%s\" - No content recorded, removing file", - pvrr->pvrr_printname); - unlink(pvrr->pvrr_filename); - } - - free(pf); - return 0; -} - diff --git a/rtp.c b/rtp.c index 658f0cf1..00e8a819 100644 --- a/rtp.c +++ b/rtp.c @@ -37,185 +37,76 @@ #include #include -#define TSBLKS_PER_PKT 7 +//static int64_t lts; +//static int pkts; - -typedef struct th_rtp_pkt { - TAILQ_ENTRY(th_rtp_pkt) trp_link; - uint32_t trp_ts; /* 90kHz clock */ - uint8_t trp_ts_valid; - uint8_t trp_blocks; /* no of 188 byte blocks stored so far */ - int64_t trp_time; - th_rtp_streamer_t *trp_trs; - - void *trp_timer; - - unsigned char trp_pkt[12 + 188 * TSBLKS_PER_PKT]; -} th_rtp_pkt_t; - - - - - -static void -rtp_send(void *aux) +void +rtp_output_ts(void *opaque, struct th_subscription *s, + uint8_t *pkt, int blocks, int64_t pcr) { - th_rtp_pkt_t *pkt = aux; - th_rtp_streamer_t *trs = pkt->trp_trs; - - TAILQ_REMOVE(&trs->trs_sendq, pkt, trp_link); - - sendto(trs->trs_fd, pkt->trp_pkt, pkt->trp_blocks * 188 + 12, 0, - (struct sockaddr *)&trs->trs_dest, sizeof(struct sockaddr_in)); - - free(pkt); - - pkt = TAILQ_FIRST(&trs->trs_sendq); - if(pkt == NULL) - return; - - pkt->trp_timer = stimer_add_hires(rtp_send, pkt, pkt->trp_time); -} - - - - -static void -rtp_schedule(th_rtp_streamer_t *trs, th_rtp_pkt_t *last, int64_t next_ts) -{ - int64_t now, sched; - th_rtp_pkt_t *first, *pkt; - uint32_t tsdelta, ts; - int ipd, ipdu; - int i = 0; - int sendq_empty = !TAILQ_FIRST(&trs->trs_sendq); - - first = TAILQ_FIRST(&trs->trs_pktq); - - assert(first->trp_ts_valid); - - tsdelta = next_ts - first->trp_ts; - ipd = tsdelta / (trs->trs_qlen + 1); - ipdu = (tsdelta * 1000000) / 90000 / (trs->trs_qlen + 1); - - now = getclock_hires(); - - do { - trs->trs_seq++; - - pkt = TAILQ_FIRST(&trs->trs_pktq); - TAILQ_REMOVE(&trs->trs_pktq, pkt, trp_link); - trs->trs_qlen--; + th_rtp_streamer_t *trs = opaque; + struct msghdr msg; + struct iovec vec[2]; + int r; - pkt->trp_pkt[0] = 0x80; - pkt->trp_pkt[1] = 33; - pkt->trp_pkt[2] = trs->trs_seq >> 8; - pkt->trp_pkt[3] = trs->trs_seq; + AVRational mpeg_tc = {1, 90000}; + char hdr[12]; - ts = first->trp_ts + i * ipd; +#if 0 + int64_t ts; + ts = getclock_hires(); + pkts++; - pkt->trp_pkt[4] = ts >> 24; - pkt->trp_pkt[5] = ts >> 16; - pkt->trp_pkt[6] = ts >> 8; - pkt->trp_pkt[7] = ts; + if(ts - lts > 100000) { + printf("%d packet per sec\n", pkts * 10); + pkts = 0; + lts = ts; + } +#endif - pkt->trp_pkt[8] = 0; - pkt->trp_pkt[9] = 0; - pkt->trp_pkt[10] = 0; - pkt->trp_pkt[11] = 0; + pcr = av_rescale_q(pcr, AV_TIME_BASE_Q, mpeg_tc); - sched = now + i * ipdu; + hdr[0] = 0x80; + hdr[1] = 33; /* M2TS */ + hdr[2] = trs->trs_seq >> 8; + hdr[3] = trs->trs_seq; - pkt->trp_time = sched; + hdr[4] = pcr >> 24; + hdr[5] = pcr >> 16; + hdr[6] = pcr >> 8; + hdr[7] = pcr; - TAILQ_INSERT_TAIL(&trs->trs_sendq, pkt, trp_link); - i++; - pkt->trp_trs = trs; - - } while(pkt != last); + hdr[8] = 0; + hdr[9] = 0; + hdr[10] = 0; + hdr[11] = 0; - printf("Sending %d packets\n", i); - if(sendq_empty) - rtp_send(TAILQ_FIRST(&trs->trs_sendq)); + vec[0].iov_base = hdr; + vec[0].iov_len = 12; + vec[1].iov_base = pkt; + vec[1].iov_len = blocks * 188; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &trs->trs_dest; + msg.msg_namelen = sizeof(struct sockaddr_in); + msg.msg_iov = vec; + msg.msg_iovlen = 2; + + r = sendmsg(trs->trs_fd, &msg, 0); + if(r < 0) + perror("sendmsg"); + + trs->trs_seq++; } -void -rtp_streamer(struct th_subscription *s, uint8_t *buf, th_pid_t *pi, - int64_t pcr) -{ - th_rtp_streamer_t *trs = s->ths_opaque; - th_rtp_pkt_t *pkt; - - if(buf == NULL) - return; - - pkt = TAILQ_LAST(&trs->trs_pktq, th_rtp_pkt_queue); - - if(trs->trs_qlen > 1 && pcr != AV_NOPTS_VALUE) { - rtp_schedule(trs, pkt, pcr); - pkt = TAILQ_LAST(&trs->trs_pktq, th_rtp_pkt_queue); - } - - if(pkt == NULL && pcr == AV_NOPTS_VALUE) - return; /* make sure first packet in queue always has pcr */ - - if(pkt == NULL || pkt->trp_blocks == TSBLKS_PER_PKT) { - pkt = malloc(sizeof(th_rtp_pkt_t)); - pkt->trp_blocks = 0; - pkt->trp_ts_valid = 0; - pkt->trp_trs = trs; - pkt->trp_timer = NULL; - - TAILQ_INSERT_TAIL(&trs->trs_pktq, pkt, trp_link); - trs->trs_qlen++; - } - - if(pkt->trp_ts_valid == 0 && pcr != AV_NOPTS_VALUE) { - pkt->trp_ts = pcr; - pkt->trp_ts_valid = 1; - } - - memcpy(&pkt->trp_pkt[12 + pkt->trp_blocks * 188], buf, 188); - pkt->trp_blocks++; -} - - void rtp_streamer_init(th_rtp_streamer_t *trs, int fd, struct sockaddr_in *dst) { - printf("RTP: Streaming to %s:%d (fd = %d)\n", - inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), fd); - trs->trs_fd = fd; trs->trs_dest = *dst; - - TAILQ_INIT(&trs->trs_pktq); - TAILQ_INIT(&trs->trs_sendq); - trs->trs_qlen = 0; trs->trs_seq = 0; } - - - - -void -rtp_streamer_deinit(th_rtp_streamer_t *trs) -{ - th_rtp_pkt_t *pkt; - - while((pkt = TAILQ_FIRST(&trs->trs_pktq)) != NULL) { - TAILQ_REMOVE(&trs->trs_pktq, pkt, trp_link); - free(pkt); - } - - while((pkt = TAILQ_FIRST(&trs->trs_sendq)) != NULL) { - TAILQ_REMOVE(&trs->trs_sendq, pkt, trp_link); - if(pkt->trp_timer != NULL) - stimer_del(pkt->trp_timer); - free(pkt); - } -} diff --git a/rtp.h b/rtp.h index c8a92d03..7279a601 100644 --- a/rtp.h +++ b/rtp.h @@ -19,27 +19,17 @@ #ifndef RTP_H_ #define RTP_H_ -TAILQ_HEAD(th_rtp_pkt_queue, th_rtp_pkt); - - typedef struct th_rtp_streamer { - struct th_rtp_pkt_queue trs_pktq; - struct th_rtp_pkt_queue trs_sendq; - int trs_qlen; - int16_t trs_seq; int trs_fd; struct sockaddr_in trs_dest; - int64_t trs_last_ts; + int16_t trs_seq; } th_rtp_streamer_t; void rtp_streamer_init(th_rtp_streamer_t *trs, int fd, struct sockaddr_in *dst); -void rtp_streamer_deinit(th_rtp_streamer_t *trs); - -void rtp_streamer(struct th_subscription *s, uint8_t *buf, th_pid_t *pi, - int64_t pcr); - +void rtp_output_ts(void *opaque, struct th_subscription *s, + uint8_t *pkt, int blocks, int64_t pcr); #endif /* RTP_H_ */ diff --git a/rtsp.c b/rtsp.c index df9e883c..b23ab29b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -17,6 +17,7 @@ */ #include +#include #include #include #include @@ -31,13 +32,13 @@ #include "tvhead.h" #include "channels.h" #include "subscriptions.h" -#include "pvr.h" #include "epg.h" #include "teletext.h" #include "dispatch.h" #include "dvb.h" #include "strtab.h" #include "rtp.h" +#include "tsmux.h" #include #include @@ -64,6 +65,8 @@ typedef struct rtsp_session { th_rtp_streamer_t rs_rtp_streamer; + void *rs_muxer; + } rtsp_session_t; @@ -96,7 +99,9 @@ typedef struct rtsp_connection { RTSP_CMD_DESCRIBE, RTSP_CMD_OPTIONS, RTSP_CMD_SETUP, + RTSP_CMD_TEARDOWN, RTSP_CMD_PLAY, + RTSP_CMD_PAUSE, } rc_cmd; @@ -106,6 +111,9 @@ typedef struct rtsp_connection { char rc_input_buf[RTSP_MAX_LINE_LEN]; struct sockaddr_in rc_from; + char *rc_logname; /* Printable name used when logging stuff related + to this connection */ + } rtsp_connection_t; @@ -114,6 +122,8 @@ static struct strtab RTSP_cmdtab[] = { { "OPTIONS", RTSP_CMD_OPTIONS }, { "SETUP", RTSP_CMD_SETUP }, { "PLAY", RTSP_CMD_PLAY }, + { "TEARDOWN", RTSP_CMD_TEARDOWN }, + { "PAUSE", RTSP_CMD_PAUSE }, }; /** @@ -137,20 +147,37 @@ rtsp_channel_by_url(char *url) return NULL; c++; - printf("URL RESOLVER: %s\n", c); - - if(sscanf(c, "chid-%d", &chid) != 1) return NULL; - printf("\t\t\t == %d\n", chid); - return channel_by_index(chid); } +/* + * Called when a subscription gets/loses access to a transport + */ +static void +rtsp_subscription_callback(struct th_subscription *s, + subscription_event_t event, void *opaque) +{ + rtsp_session_t *rs = opaque; + + switch(event) { + case TRANSPORT_AVAILABLE: + assert(rs->rs_muxer == NULL); + rs->rs_muxer = ts_muxer_init(s, rtp_output_ts, &rs->rs_rtp_streamer, 0); + break; + + case TRANSPORT_UNAVAILABLE: + assert(rs->rs_muxer != NULL); + ts_muxer_deinit(rs->rs_muxer); + rs->rs_muxer = NULL; + break; + } +} + /** * Create an RTSP session */ - static rtsp_session_t * rtsp_session_create(th_channel_t *ch, struct sockaddr_in *dst) { @@ -190,7 +217,6 @@ rtsp_session_create(th_channel_t *ch, struct sockaddr_in *dst) getsockname(rs->rs_fd[0], (struct sockaddr *)&sin, &slen); rs->rs_server_port[0] = ntohs(sin.sin_port); - printf("rtpserver: bound to port %d\n", rs->rs_server_port[0]); rs->rs_server_port[1] = rs->rs_server_port[0] + 1; sin.sin_port = htons(rs->rs_server_port[1]); @@ -203,14 +229,14 @@ rtsp_session_create(th_channel_t *ch, struct sockaddr_in *dst) continue; } - printf("bound server_port %d-%d\n", - rs->rs_server_port[0], rs->rs_server_port[1]); LIST_INSERT_HEAD(&rtsp_sessions, rs, rs_global_link); - rtp_streamer_init(&rs->rs_rtp_streamer, rs->rs_fd[0], dst); + rs->rs_s = subscription_create(ch, 600, "RTSP", + rtsp_subscription_callback, rs); - rs->rs_s = subscription_create(ch, &rs->rs_rtp_streamer, - rtp_streamer, 600, "RTSP"); + /* Initialize RTP */ + + rtp_streamer_init(&rs->rs_rtp_streamer, rs->rs_fd[0], dst); return rs; } @@ -221,16 +247,16 @@ rtsp_session_create(th_channel_t *ch, struct sockaddr_in *dst) /** * Destroy an RTSP session */ - static void rtsp_session_destroy(rtsp_session_t *rs) { - subscription_unsubscribe(rs->rs_s); + subscription_unsubscribe(rs->rs_s); /* will call subscription_callback + with TRANSPORT_UNAVAILABLE if + we are hooked on a transport */ close(rs->rs_fd[0]); close(rs->rs_fd[1]); LIST_REMOVE(rs, rs_global_link); LIST_REMOVE(rs, rs_con_link); - rtp_streamer_deinit(&rs->rs_rtp_streamer); free(rs); } @@ -238,7 +264,6 @@ rtsp_session_destroy(rtsp_session_t *rs) /* * Prints data on rtsp connection */ - static void rcprintf(rtsp_connection_t *rc, const char *fmt, ...) { @@ -256,7 +281,6 @@ rcprintf(rtsp_connection_t *rc, const char *fmt, ...) /* * Delete all arguments associated with an RTSP connection */ - static void rtsp_con_flush_args(rtsp_connection_t *rc) { @@ -273,7 +297,6 @@ rtsp_con_flush_args(rtsp_connection_t *rc) /** * Find an argument associated with an RTSP connection */ - static char * rtsp_con_get_arg(rtsp_connection_t *rc, char *name) { @@ -288,11 +311,12 @@ rtsp_con_get_arg(rtsp_connection_t *rc, char *name) /** * Set an argument associated with an RTSP connection */ - static void rtsp_con_set_arg(rtsp_connection_t *rc, char *key, char *val) { rtsp_arg_t *ra; + char buf[100]; + LIST_FOREACH(ra, &rc->rc_args, link) if(!strcasecmp(ra->key, key)) break; @@ -305,13 +329,21 @@ rtsp_con_set_arg(rtsp_connection_t *rc, char *key, char *val) free(ra->val); } ra->val = strdup(val); + + if(!strcasecmp(key, "User-Agent")) { + free(rc->rc_logname); + + snprintf(buf, sizeof(buf), "%s:%d [%s]", + inet_ntoa(rc->rc_from.sin_addr), ntohs(rc->rc_from.sin_port), + val); + rc->rc_logname = strdup(buf); + } } /* - * + * Split a string in components delimited by 'delimiter' */ - static int tokenize(char *buf, char **vec, int vecsize, int delimiter) { @@ -338,7 +370,6 @@ tokenize(char *buf, char **vec, int vecsize, int delimiter) /* * RTSP return code to string */ - static const char * rtsp_err2str(int err) { @@ -361,52 +392,86 @@ rtsp_err2str(int err) } /* - * + * Return an error */ -static int +static void rtsp_reply_error(rtsp_connection_t *rc, int error) { char *c; + const char *errstr = rtsp_err2str(error); - syslog(LOG_NOTICE, "RTSP error %d %s", error, rtsp_err2str(error)); + syslog(LOG_INFO, "rtsp: %s: %s", rc->rc_logname, errstr); - rcprintf(rc, "RTSP/1.0 %d %s\r\n", error, rtsp_err2str(error)); + rcprintf(rc, "RTSP/1.0 %d %s\r\n", error, errstr); if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) rcprintf(rc, "CSeq: %s\r\n", c); rcprintf(rc, "\r\n"); - return ECONNRESET; } +/* + * Find a session pointed do by the current connection + */ +static rtsp_session_t * +rtsp_get_session(rtsp_connection_t *rc) +{ + char *ses; + int sesid; + rtsp_session_t *rs; + th_channel_t *ch; + + if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + return NULL; + } + + + if((ses = rtsp_con_get_arg(rc, "session")) == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_SESSION); + return NULL; + } + + sesid = atoi(ses); + LIST_FOREACH(rs, &rtsp_sessions, rs_global_link) + if(rs->rs_id == sesid) + break; + + if(rs == NULL) + rtsp_reply_error(rc, RTSP_STATUS_SESSION); + + return rs; +} /* * RTSP PLAY */ -static int +static void rtsp_cmd_play(rtsp_connection_t *rc) { - char *ses, *c; - int sesid; + char *c; + int64_t start; rtsp_session_t *rs; - th_channel_t *ch; - - if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); - - printf(">>> PLAY\n"); - - if((ses = rtsp_con_get_arg(rc, "session:")) == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_SESSION); - - sesid = atoi(ses); - printf("\t\tsesid = %u\n", sesid); - LIST_FOREACH(rs, &rtsp_sessions, rs_global_link) - if(rs->rs_id == sesid) - break; - + + rs = rtsp_get_session(rc); if(rs == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_SESSION); + return; + + if((c = rtsp_con_get_arg(rc, "range")) != NULL) { + start = AV_NOPTS_VALUE; + } else { + start = AV_NOPTS_VALUE; + } + + if(rs->rs_muxer == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + return; + } + + ts_muxer_play(rs->rs_muxer, start); + + syslog(LOG_INFO, "rtsp: %s: Starting playback of %s", + rc->rc_logname, rs->rs_s->ths_channel->ch_name); rcprintf(rc, "RTSP/1.0 200 OK\r\n" @@ -417,15 +482,48 @@ rtsp_cmd_play(rtsp_connection_t *rc) rcprintf(rc, "CSeq: %s\r\n", c); rcprintf(rc, "\r\n"); +} - return 0; +/* + * RTSP PAUSE + */ + +static void +rtsp_cmd_pause(rtsp_connection_t *rc) +{ + char *c; + rtsp_session_t *rs; + + rs = rtsp_get_session(rc); + if(rs == NULL) + return; + + if(rs->rs_muxer == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + return; + } + + ts_muxer_pause(rs->rs_muxer); + + syslog(LOG_INFO, "rtsp: %s: Pausing playback of %s", + rc->rc_logname, rs->rs_s->ths_channel->ch_name); + + rcprintf(rc, + "RTSP/1.0 200 OK\r\n" + "Session: %u\r\n", + rs->rs_id); + + if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) + rcprintf(rc, "CSeq: %s\r\n", c); + + rcprintf(rc, "\r\n"); } /* * RTSP SETUP */ -static int +static void rtsp_cmd_setup(rtsp_connection_t *rc) { char *transports[10]; @@ -439,15 +537,18 @@ rtsp_cmd_setup(rtsp_connection_t *rc) th_channel_t *ch; struct sockaddr_in dst; - if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + return; + } client_ports[0] = 0; client_ports[1] = 0; - printf(">>> SETUP\n"); - if((t = rtsp_con_get_arg(rc, "transport:")) == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_TRANSPORT); + if((t = rtsp_con_get_arg(rc, "transport")) == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_TRANSPORT); + return; + } nt = tokenize(t, transports, 10, ','); @@ -471,8 +572,6 @@ rtsp_cmd_setup(rtsp_connection_t *rc) if((navp = tokenize(params[j], avp, 2, '=')) == 0) continue; - printf("%s = %s\n", avp[0], navp == 2 ? avp[1] : ""); - if(navp == 1 && !strcmp(avp[0], "unicast")) { ismulticast = 0; } else if(navp == 2 && !strcmp(avp[0], "client_port")) { @@ -481,28 +580,26 @@ rtsp_cmd_setup(rtsp_connection_t *rc) if(nports > 1) client_ports[1] = atoi(ports[1]); } } - printf("\t%d %d %d\n", - ismulticast, client_ports[0], client_ports[1]); - if(!ismulticast && client_ports[0] && client_ports[1]) break; } - if(i == nt) /* couldnt find a suitable transport */ { - printf(">>> SETUP; no transport\n"); - return rtsp_reply_error(rc, RTSP_STATUS_TRANSPORT); + if(i == nt) { + /* couldnt find a suitable transport */ + rtsp_reply_error(rc, RTSP_STATUS_TRANSPORT); + return; } dst = rc->rc_from; dst.sin_port = htons(client_ports[0]); - if((rs = rtsp_session_create(ch, &dst)) == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_INTERNAL); + if((rs = rtsp_session_create(ch, &dst)) == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_INTERNAL); + return; + } LIST_INSERT_HEAD(&rc->rc_sessions, rs, rs_con_link); - printf(">>> SETUP, rending reply\n"); - rcprintf(rc, "RTSP/1.0 200 OK\r\n" "Session: %u\r\n" @@ -518,7 +615,6 @@ rtsp_cmd_setup(rtsp_connection_t *rc) rcprintf(rc, "CSeq: %s\r\n", c); rcprintf(rc, "\r\n"); - return 0; } @@ -527,15 +623,17 @@ rtsp_cmd_setup(rtsp_connection_t *rc) * RTSP DESCRIBE */ -static int +static void rtsp_cmd_describe(rtsp_connection_t *rc) { char sdpreply[1000]; th_channel_t *ch; char *c; - if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + return; + } snprintf(sdpreply, sizeof(sdpreply), "v=0\r\n" @@ -545,7 +643,6 @@ rtsp_cmd_describe(rtsp_connection_t *rc) "m=video 0 RTP/AVP 33\r\n", ch->ch_name); - printf("sdpreply = %s\n", sdpreply); rcprintf(rc, "RTSP/1.0 200 OK\r\n" @@ -557,21 +654,21 @@ rtsp_cmd_describe(rtsp_connection_t *rc) rcprintf(rc, "CSeq: %s\r\n", c); rcprintf(rc, "\r\n%s", sdpreply); - return 0; } /* * RTSP OPTIONS */ - -static int +static void rtsp_cmd_options(rtsp_connection_t *rc) { char *c; - if(strcmp(rc->rc_url, "*") && rtsp_channel_by_url(rc->rc_url) == NULL) - return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + if(strcmp(rc->rc_url, "*") && rtsp_channel_by_url(rc->rc_url) == NULL) { + rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + return; + } rcprintf(rc, "RTSP/1.0 200 OK\r\n" @@ -580,9 +677,33 @@ rtsp_cmd_options(rtsp_connection_t *rc) if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) rcprintf(rc, "CSeq: %s\r\n", c); rcprintf(rc, "\r\n"); - return 0; } +/* + * RTSP TEARDOWN + */ +static void +rtsp_cmd_teardown(rtsp_connection_t *rc) +{ + rtsp_session_t *rs; + char *c; + + rs = rtsp_get_session(rc); + if(rs == NULL) + return; + + rcprintf(rc, + "RTSP/1.0 200 OK\r\n" + "Session: %u\r\n", + rs->rs_id); + + if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) + rcprintf(rc, "CSeq: %s\r\n", c); + + rcprintf(rc, "\r\n"); + + rtsp_session_destroy(rs); +} /* * RTSP connection state machine & parser @@ -591,7 +712,7 @@ static int rtsp_con_parse(rtsp_connection_t *rc, char *buf) { int n; - char *argv[3]; + char *argv[3], *c; switch(rc->rc_state) { case RTSP_CON_WAIT_REQUEST: @@ -601,7 +722,6 @@ rtsp_con_parse(rtsp_connection_t *rc, char *buf) rc->rc_url = NULL; } - printf(">>>> %s\n", buf); n = tokenize(buf, argv, 3, -1); if(n < 3) @@ -619,18 +739,16 @@ rtsp_con_parse(rtsp_connection_t *rc, char *buf) case RTSP_CON_READ_HEADER: if(*buf == 0) { rc->rc_state = RTSP_CON_WAIT_REQUEST; - printf("cmd = %d\n", rc->rc_cmd); switch(rc->rc_cmd) { default: - return rtsp_reply_error(rc, RTSP_STATUS_METHOD); - case RTSP_CMD_DESCRIBE: - return rtsp_cmd_describe(rc); - case RTSP_CMD_SETUP: - return rtsp_cmd_setup(rc); - case RTSP_CMD_PLAY: - return rtsp_cmd_play(rc); - case RTSP_CMD_OPTIONS: - return rtsp_cmd_options(rc); + rtsp_reply_error(rc, RTSP_STATUS_METHOD); + break; + case RTSP_CMD_DESCRIBE: rtsp_cmd_describe(rc); break; + case RTSP_CMD_SETUP: rtsp_cmd_setup(rc); break; + case RTSP_CMD_PLAY: rtsp_cmd_play(rc); break; + case RTSP_CMD_PAUSE: rtsp_cmd_pause(rc); break; + case RTSP_CMD_OPTIONS: rtsp_cmd_options(rc); break; + case RTSP_CMD_TEARDOWN: rtsp_cmd_teardown(rc); break; } break; @@ -640,8 +758,10 @@ rtsp_con_parse(rtsp_connection_t *rc, char *buf) if(n < 2) break; - printf("'%s' '%s'\n", argv[0], argv[1]); - + c = strrchr(argv[0], ':'); + if(c == NULL) + break; + *c = 0; rtsp_con_set_arg(rc, argv[0], argv[1]); break; @@ -659,14 +779,16 @@ static void rtsp_con_teardown(rtsp_connection_t *rc, int err) { rtsp_session_t *rs; - syslog(LOG_INFO, "RTSP disconnected -- %s", strerror(err)); - + syslog(LOG_INFO, "rtsp: %s: disconnected -- %s", + rc->rc_logname, strerror(err)); + while((rs = LIST_FIRST(&rc->rc_sessions)) != NULL) rtsp_session_destroy(rs); close(dispatch_delfd(rc->rc_dispatch_handle)); free(rc->rc_url); + free(rc->rc_logname); rtsp_con_flush_args(rc); free(rc); } @@ -791,7 +913,9 @@ rtsp_connect_callback(int events, void *opaque, int fd) snprintf(txt, sizeof(txt), "%s:%d", inet_ntoa(from.sin_addr), ntohs(from.sin_port)); - syslog(LOG_INFO, "Got RTSP/TCP connection from %s", txt); + rc->rc_logname = strdup(txt); + + syslog(LOG_INFO, "rtsp: %s: connected", rc->rc_logname); rc->rc_from = from; @@ -821,11 +945,12 @@ rtsp_start(void) fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); - syslog(LOG_INFO, "Listening for RTSP/TCP connections on %s:%d", + syslog(LOG_INFO, "rtsp: Listening for RTSP/TCP connections on %s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { - syslog(LOG_ERR, "Unable to bind socket for incomming RTSP/TCP connections" + syslog(LOG_ERR, + "rtsp: Unable to bind socket for incomming RTSP/TCP connections" "%s:%d -- %s", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), strerror(errno)); diff --git a/subscriptions.c b/subscriptions.c index 547e7a76..c89bdc28 100644 --- a/subscriptions.c +++ b/subscriptions.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include #include #include @@ -42,6 +43,7 @@ #include "dvb_dvr.h" #include "teletext.h" #include "transports.h" +#include "subscriptions.h" #include "v4l.h" #include "dvb_dvr.h" @@ -49,6 +51,8 @@ #include "psi.h" struct th_subscription_list subscriptions; +static dtimer_t auto_reschedule_timer; + static void subscription_reschedule(void) @@ -67,39 +71,40 @@ subscription_reschedule(void) s->ths_transport = t; LIST_INSERT_HEAD(&t->tht_subscriptions, s, ths_transport_link); + s->ths_callback(s, TRANSPORT_AVAILABLE, s->ths_opaque); } } - - static void -auto_reschedule(void *aux) +auto_reschedule(void *aux, int64_t now) { - stimer_add(auto_reschedule, NULL, 10); + dtimer_arm(&auto_reschedule_timer, auto_reschedule, NULL, 10); subscription_reschedule(); } - +void +subscription_stop(th_subscription_t *s) +{ + s->ths_callback(s, TRANSPORT_UNAVAILABLE, s->ths_opaque); + LIST_REMOVE(s, ths_transport_link); + s->ths_transport = NULL; +} void subscription_unsubscribe(th_subscription_t *s) { - s->ths_callback(s, NULL, NULL, AV_NOPTS_VALUE); - + th_transport_t *t = s->ths_transport; LIST_REMOVE(s, ths_global_link); LIST_REMOVE(s, ths_channel_link); - if(s->ths_transport != NULL) { - LIST_REMOVE(s, ths_transport_link); - transport_purge(s->ths_transport); + if(t != NULL) { + subscription_stop(s); + transport_purge(t); } - if(s->ths_pkt != NULL) - free(s->ths_pkt); - free(s->ths_title); free(s); @@ -118,22 +123,19 @@ subscription_sort(th_subscription_t *a, th_subscription_t *b) th_subscription_t * -subscription_create(th_channel_t *ch, void *opaque, - void (*callback)(struct th_subscription *s, - uint8_t *pkt, th_pid_t *pi, int64_t pcr), - unsigned int weight, - const char *name) +subscription_create(th_channel_t *ch, unsigned int weight, const char *name, + subscription_callback_t *cb, void *opaque) { th_subscription_t *s; s = malloc(sizeof(th_subscription_t)); - s->ths_pkt = NULL; - s->ths_callback = callback; - s->ths_opaque = opaque; - s->ths_title = strdup(name); + s->ths_callback = cb; + s->ths_opaque = opaque; + s->ths_title = strdup(name); s->ths_total_err = 0; + s->ths_weight = weight; + time(&s->ths_start); - s->ths_weight = weight; LIST_INSERT_SORTED(&subscriptions, s, ths_global_link, subscription_sort); s->ths_channel = ch; @@ -168,5 +170,6 @@ subscription_set_weight(th_subscription_t *s, unsigned int weight) void subscriptions_init(void) { - stimer_add(auto_reschedule, NULL, 60); + dtimer_arm(&auto_reschedule_timer, auto_reschedule, NULL, 10); } + diff --git a/subscriptions.h b/subscriptions.h index 4e37185e..d81efdfc 100644 --- a/subscriptions.h +++ b/subscriptions.h @@ -25,15 +25,13 @@ void subscription_unsubscribe(th_subscription_t *s); void subscription_set_weight(th_subscription_t *s, unsigned int weight); -typedef void (subscription_callback_t)(struct th_subscription *s, - uint8_t *pkt, th_pid_t *pi, - int64_t pcr); - -th_subscription_t *subscription_create(th_channel_t *ch, void *opaque, - subscription_callback_t *ths_callback, - unsigned int weight, - const char *name); +th_subscription_t *subscription_create(th_channel_t *ch, unsigned int weight, + const char *name, + subscription_callback_t *cb, + void *opaque); void subscriptions_init(void); +void subscription_stop(th_subscription_t *s); + #endif /* SUBSCRIPTIONS_H */ diff --git a/transports.c b/transports.c index 4a11dee4..35d60a79 100644 --- a/transports.c +++ b/transports.c @@ -17,6 +17,7 @@ */ #include +#include #include #include @@ -43,15 +44,26 @@ #include "teletext.h" #include "transports.h" #include "subscriptions.h" +#include "tsdemux.h" #include "v4l.h" #include "dvb_dvr.h" #include "iptv_input.h" #include "psi.h" +#include "pes.h" +#include "buffer.h" + +static dtimer_t transport_monitor_timer; + + void transport_purge(th_transport_t *t) { + th_stream_t *st; + th_pkt_t *pkt, *next; + + if(LIST_FIRST(&t->tht_subscriptions)) return; @@ -76,15 +88,107 @@ transport_purge(th_transport_t *t) } t->tht_tt_commercial_advice = COMMERCIAL_UNKNOWN; + + + /* + * Clean up each stream + */ + + LIST_FOREACH(st, &t->tht_streams, st_link) { + + if(st->st_parser != NULL) + av_parser_close(st->st_parser); + + if(st->st_ctx != NULL) + avcodec_close(st->st_ctx); + + /* Clear reassembly buffer */ + + free(st->st_buffer); + st->st_buffer = NULL; + st->st_buffer_size = 0; + st->st_buffer_ptr = 0; + + + /* Clear DTS queue */ + + while((pkt = TAILQ_FIRST(&st->st_dtsq)) != NULL) { + TAILQ_REMOVE(&st->st_dtsq, pkt, pkt_queue_link); + assert(pkt->pkt_refcount == 1); + pkt_deref(pkt); + } + st->st_dtsq_len = 0; + + /* Clear PTS queue */ + + while((pkt = TAILQ_FIRST(&st->st_ptsq)) != NULL) { + TAILQ_REMOVE(&st->st_ptsq, pkt, pkt_queue_link); + assert(pkt->pkt_refcount == 1); + pkt_deref(pkt); + } + st->st_ptsq_len = 0; + + /* Clear durationq */ + + while((pkt = TAILQ_FIRST(&st->st_durationq)) != NULL) { + TAILQ_REMOVE(&st->st_durationq, pkt, pkt_queue_link); + assert(pkt->pkt_refcount == 1); + pkt_deref(pkt); + } + + /* Flush framestore */ + + for(pkt = TAILQ_FIRST(&st->st_pktq); pkt != NULL; pkt = next) { + next = TAILQ_NEXT(pkt, pkt_queue_link); + pkt_unstore(pkt); + } + assert(TAILQ_FIRST(&st->st_pktq) == NULL); + } } - - +/* + * + */ static int transport_start(th_transport_t *t, unsigned int weight) { + th_stream_t *st; + AVCodec *c; + enum CodecID id; + t->tht_monitor_suspend = 10; + t->tht_dts_start = AV_NOPTS_VALUE; + + LIST_FOREACH(st, &t->tht_streams, st_link) { + + st->st_dts = AV_NOPTS_VALUE; + st->st_last_dts = 0; + st->st_dts_u = 0; + + /* Open ffmpeg context and parser */ + + switch(st->st_type) { + case HTSTV_MPEG2VIDEO: id = CODEC_ID_MPEG2VIDEO; break; + case HTSTV_MPEG2AUDIO: id = CODEC_ID_MP3; break; + case HTSTV_H264: id = CODEC_ID_H264; break; + case HTSTV_AC3: id = CODEC_ID_AC3; break; + default: id = CODEC_ID_NONE; break; + } + + st->st_ctx = NULL; + st->st_parser = NULL; + + if(id != CODEC_ID_NONE) { + c = avcodec_find_decoder(id); + if(c != NULL) { + st->st_ctx = avcodec_alloc_context(); + avcodec_open(st->st_ctx, c); + st->st_parser = av_parser_init(id); + } + } + } + switch(t->tht_type) { #ifdef ENABLE_INPUT_DVB @@ -146,11 +250,8 @@ transport_flush_subscribers(th_transport_t *t) { th_subscription_t *s; - while((s = LIST_FIRST(&t->tht_subscriptions)) != NULL) { - LIST_REMOVE(s, ths_transport_link); - s->ths_transport = NULL; - s->ths_callback(s, NULL, NULL, AV_NOPTS_VALUE); - } + while((s = LIST_FIRST(&t->tht_subscriptions)) != NULL) + subscription_stop(s); } unsigned int @@ -174,12 +275,12 @@ transport_compute_weight(struct th_transport_list *head) static void -transport_monitor(void *aux) +transport_monitor(void *aux, int64_t now) { th_transport_t *t = aux; int v; - stimer_add(transport_monitor, t, 1); + dtimer_arm(&transport_monitor_timer, transport_monitor, t, 1); if(t->tht_status == TRANSPORT_IDLE) return; @@ -245,26 +346,33 @@ transport_monitor_init(th_transport_t *t) avgstat_init(&t->tht_cc_errors, 3600); avgstat_init(&t->tht_rate, 10); - stimer_add(transport_monitor, t, 5); + dtimer_arm(&transport_monitor_timer, transport_monitor, t, 5); } -th_pid_t * -transport_add_pid(th_transport_t *t, uint16_t pid, tv_streamtype_t type) + +th_stream_t * +transport_add_stream(th_transport_t *t, int pid, tv_streamtype_t type) { - th_pid_t *pi; + th_stream_t *st; int i = 0; - LIST_FOREACH(pi, &t->tht_pids, tp_link) { + + LIST_FOREACH(st, &t->tht_streams, st_link) { i++; - if(pi->tp_pid == pid) - return pi; + if(pid != -1 && st->st_pid == pid) + return st; } - pi = calloc(1, sizeof(th_pid_t)); - pi->tp_index = i; - pi->tp_pid = pid; - pi->tp_type = type; - pi->tp_demuxer_fd = -1; + st = calloc(1, sizeof(th_stream_t)); + st->st_index = i; + st->st_pid = pid; + st->st_type = type; + st->st_demuxer_fd = -1; + LIST_INSERT_HEAD(&t->tht_streams, st, st_link); - LIST_INSERT_HEAD(&t->tht_pids, pi, tp_link); - return pi; + TAILQ_INIT(&st->st_dtsq); + TAILQ_INIT(&st->st_ptsq); + TAILQ_INIT(&st->st_durationq); + TAILQ_INIT(&st->st_pktq); + + return st; } diff --git a/transports.h b/transports.h index 5568cfd3..7a2bb999 100644 --- a/transports.h +++ b/transports.h @@ -35,5 +35,8 @@ th_transport_t *transport_find(th_channel_t *ch, unsigned int weight); void transport_purge(th_transport_t *t); +th_stream_t *transport_add_stream(th_transport_t *t, int pid, + tv_streamtype_t type); + #endif /* TRANSPORTS_H */ diff --git a/tsdemux.c b/tsdemux.c new file mode 100644 index 00000000..9d1584ab --- /dev/null +++ b/tsdemux.c @@ -0,0 +1,170 @@ +/* + * tvheadend, MPEG transport stream demuxer + * 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 . + */ +#define _GNU_SOURCE +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +#include "tvhead.h" +#include "dispatch.h" +#include "teletext.h" +#include "transports.h" +#include "subscriptions.h" +#include "pes.h" +#include "psi.h" +#include "buffer.h" +#include "tsdemux.h" + + +/* + * TS packet reassembly + */ +static void +ts_reassembly(th_transport_t *t, th_stream_t *st, uint8_t *data, int len, + int pusi, int err) +{ + if(pusi) { + if(st->st_buffer_ptr > 6) { + if(pes_packet_input(t, st, st->st_buffer + 6, + st->st_buffer_ptr - 6) == 0) + st->st_buffer = NULL; /* Memory was consumed by pes_packet_input() */ + } + + st->st_buffer_ptr = 0; + st->st_buffer_errors = 0; + + if(st->st_buffer == NULL) { + + if(st->st_buffer_size < 1000) + st->st_buffer_size = 4000; + + st->st_buffer = malloc(st->st_buffer_size); + } + } + + if(st->st_buffer == NULL) + return; + + st->st_buffer_errors += err; + + if(st->st_buffer_ptr + len >= st->st_buffer_size) { + st->st_buffer_size += len * 4; + st->st_buffer = realloc(st->st_buffer, st->st_buffer_size); + } + + memcpy(st->st_buffer + st->st_buffer_ptr, data, len); + st->st_buffer_ptr += len; +} + + +/* + * Process transport stream packets + */ +void +ts_recv_packet(th_transport_t *t, int pid, uint8_t *tsb) +{ + th_stream_t *st = NULL; + int cc, err = 0, afc, afl = 0; + int len, pusi; + + LIST_FOREACH(st, &t->tht_streams, st_link) + if(st->st_pid == pid) + break; + + if(st == NULL) + return; + + avgstat_add(&t->tht_rate, 188, dispatch_clock); + + afc = (tsb[3] >> 4) & 3; + + if(afc & 1) { + cc = tsb[3] & 0xf; + if(st->st_cc_valid && cc != st->st_cc) { + /* Incorrect CC */ + avgstat_add(&t->tht_cc_errors, 1, dispatch_clock); + err = 1; + } + st->st_cc_valid = 1; + st->st_cc = (cc + 1) & 0xf; + } + + if(afc & 2) + afl = tsb[4] + 1; + + pusi = tsb[1] & 0x40; + + switch(st->st_type) { + + case HTSTV_TABLE: + if(st->st_section == NULL) + st->st_section = calloc(1, sizeof(struct psi_section)); + + afl += 4; + if(err || afl >= 188) { + st->st_section->ps_offset = -1; /* hold parser until next pusi */ + break; + } + + if(pusi) { + len = tsb[afl++]; + if(len > 0) { + if(len > 188 - afl) + break; + if(!psi_section_reassemble(st->st_section, tsb + afl, len, 0, 1)) + st->st_got_section(t, st, st->st_section->ps_data, + st->st_section->ps_offset); + + afl += len; + } + } + + if(!psi_section_reassemble(st->st_section, tsb + afl, 188 - afl, pusi, 1)) + st->st_got_section(t, st, st->st_section->ps_data, + st->st_section->ps_offset); + break; + + case HTSTV_TELETEXT: + teletext_input(t, tsb); + break; + + default: + afl += 4; + ts_reassembly(t, st, tsb + afl, 188 - afl, pusi, err); + break; + } +} diff --git a/pvr_rec.h b/tsdemux.h similarity index 80% rename from pvr_rec.h rename to tsdemux.h index e0a7e9e5..03296171 100644 --- a/pvr_rec.h +++ b/tsdemux.h @@ -1,5 +1,5 @@ /* - * Private Video Recorder, Recording functions + * tvheadend, MPEG transport stream functions * Copyright (C) 2007 Andreas Öman * * This program is free software: you can redistribute it and/or modify @@ -16,9 +16,9 @@ * along with this program. If not, see . */ -#ifndef PVR_REC_H -#define PVR_REC_H +#ifndef TSDEMUX_H +#define TSDEMUX_H -void *pvr_recorder_thread(void *aux); +void ts_recv_packet(th_transport_t *t, int pid, uint8_t *tsb); -#endif /* PVR_H */ +#endif /* TSDEMUX_H */ diff --git a/tsmux.c b/tsmux.c new file mode 100644 index 00000000..b4c5afef --- /dev/null +++ b/tsmux.c @@ -0,0 +1,864 @@ +/* + * tvheadend, MPEG transport stream 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 . + */ +#define _GNU_SOURCE +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +#include "tvhead.h" +#include "dispatch.h" +#include "teletext.h" +#include "transports.h" +#include "subscriptions.h" +#include "pes.h" +#include "psi.h" +#include "buffer.h" +#include "tsmux.h" + +#define PES_HEADER_SIZE 19 + +static void ts_muxer_relock(th_muxer_t *tm, uint64_t pcr); + + +/* + * Return the deadline for a given packet + */ +static int64_t +muxer_pkt_deadline(th_pkt_t *pkt, th_muxstream_t *tms) +{ + th_stream_t *st = pkt->pkt_stream; + int64_t r; + + r = pkt->pkt_dts; + if(st != NULL) + r -= st->st_peak_presentation_delay; + + r -= tms->tms_mux_offset; + return r; +} + + +/* + * + */ +static void +tms_set_curpkt(th_muxstream_t *tms, th_pkt_t *pkt) +{ + if(tms->tms_curpkt != NULL) + pkt_deref(tms->tms_curpkt); + + tms->tms_offset = 0; + + if(pkt != NULL) { + tms->tms_curpkt = pkt_ref(pkt); + } else { + tms->tms_curpkt = NULL; + } +} + +/* + * + */ +static void +tms_stream_set_stale(th_muxer_t *tm, th_muxstream_t *tms, int64_t pcr) +{ + tms->tms_nextblock = INT64_MAX; + tms->tms_staletime = pcr; + LIST_REMOVE(tms, tms_muxer_link); + LIST_INSERT_HEAD(&tm->tm_stale_streams, tms, tms_muxer_link); + tms_set_curpkt(tms, NULL); +} + +/* + * + */ +static void +tms_stream_add_meta(th_muxer_t *tm, th_muxstream_t *tms, th_pkt_t *pkt) +{ + LIST_REMOVE(tms, tms_muxer_link); + LIST_INSERT_HEAD(&tm->tm_active_streams, tms, tms_muxer_link); + tms_set_curpkt(tms, pkt); + tms->tms_block_interval = 0; + tms->tms_nextblock = 0; +} + +/* + * + */ +static void +tms_stream_set_active(th_muxer_t *tm, th_muxstream_t *tms, th_pkt_t *pkt, + int64_t pcr) +{ + int l, dt; + int64_t dl; + + assert(pkt->pkt_duration > 0); + + tms->tms_nextblock = pcr; + dl = muxer_pkt_deadline(pkt, tms); + dt = dl - pcr; + l = (pkt_len(pkt) + PES_HEADER_SIZE) / 188; + + tms->tms_dl = dl; + tms->tms_block_interval = dt / l; + +#if 0 + if(tms->tms_stream) + printf("%-15s dts=%lld, ft=%d, bi = %5d dt = %6d dl = %lld pcr = %lld " + "size=%d\n", + htstvstreamtype2txt(tms->tms_stream->st_type), + pkt->pkt_dts, + pkt->pkt_frametype, tms->tms_block_interval, dt, dl, pcr, + pkt_len(pkt) + PES_HEADER_SIZE); +#endif + + if(tms->tms_block_interval < 10) + tms->tms_block_interval = 10; + + LIST_REMOVE(tms, tms_muxer_link); + LIST_INSERT_HEAD(&tm->tm_active_streams, tms, tms_muxer_link); + tms_set_curpkt(tms, pkt); + tms->tms_blockcnt = 0; +} + + + + + +/* + * Generates a 188 bytes TS packet in 'tsb' + */ +static int +ts_make_pkt(th_muxer_t *tm, th_muxstream_t *tms, uint8_t *tsb, int64_t pcr) +{ + uint8_t *tsb0 = tsb; + th_pkt_t *pkt = tms->tms_curpkt; + uint8_t *src; + int tsrem; /* Remaining bytes in tspacket */ + int frrem; /* Remaining bytes in frame */ + int64_t ts; + int pad, cc, len; + uint16_t u16; + int is_section = pkt->pkt_stream == NULL; + int header_len = 0; + int dumppkt = 0; + AVRational mpeg_tc = {1, 90000}; + + if(pkt_payload(pkt) == NULL) + return -1; + + frrem = pkt_len(pkt) - tms->tms_offset; + assert(frrem > 0); + + cc = tms->tms_cc++; + tsrem = 184; + + if(tms->tms_offset == 0) { + /* When writing the packet header, shave of a bit of available + payload size */ + if(is_section) { + header_len = 1; + } else { + header_len = PES_HEADER_SIZE; + } + tsrem -= header_len; + } + + if(tm->tm_flags & TM_HTSCLIENTMODE) { + /* UGLY */ + if(tms->tms_stream) + *tsb++ = tms->tms_stream->st_type; + else + *tsb++ = 0xff; + + } else { + /* TS marker */ + *tsb++ = 0x47; + } + + /* Write PID and optionally payload unit start indicator */ + *tsb++ = tms->tms_index >> 8 | (tms->tms_offset ? 0 : 0x40); + *tsb++ = tms->tms_index; + + + + /* Compute amout of padding needed */ + pad = tsrem - frrem; + + + if(pcr != AV_NOPTS_VALUE) { + + /* Insert PCR */ + + tsrem -= 8; + pad -= 8; + if(pad < 0) + pad = 0; + + *tsb++ = (cc & 0xf) | 0x30; + *tsb++ = 7 + pad; + *tsb++ = 0x10; /* PCR flag */ + + ts = av_rescale_q(pcr, AV_TIME_BASE_Q, mpeg_tc); + *tsb++ = ts >> 25; + *tsb++ = ts >> 17; + *tsb++ = ts >> 9; + *tsb++ = ts >> 1; + *tsb++ = (ts & 1) << 7; + *tsb++ = 0; + + memset(tsb, 0xff, pad); + tsb += pad; + tsrem -= pad; + + } else if(pad > 0) { + /* Must pad TS packet */ + + *tsb++ = (cc & 0xf) | 0x30; + tsrem -= pad; + *tsb++ = --pad; + + memset(tsb, 0x00, pad); + tsb += pad; + } else { + *tsb++ = (cc & 0xf) | 0x10; + } + + if(tms->tms_offset == 0) { + if(!is_section) { + + /* First TS packet for this frame, write PES headers */ + + /* Write startcode */ + + *tsb++ = 0; + *tsb++ = 0; + *tsb++ = tms->tms_sc >> 8; + *tsb++ = tms->tms_sc; + + /* Write total frame length (without accounting for startcode and + length field itself */ + + len = pkt_len(pkt) + header_len - 6; + + if(len > 65535) { + /* It's okay to write len as 0 in transport streams, + but only for video frames, and i dont expect any of the + audio frames to exceed 64k + */ + len = 0; + } + + *tsb++ = len >> 8; + *tsb++ = len; + + *tsb++ = 0x80; /* MPEG2 */ + *tsb++ = 0xc0; /* pts & dts is present */ + *tsb++ = 10; /* length of header (pts & dts) */ + + /* Write PTS */ + + ts = av_rescale_q(pkt->pkt_pts, AV_TIME_BASE_Q, mpeg_tc); + *tsb++ = (((ts >> 30) & 7) << 1) | 1; + u16 = (((ts >> 15) & 0x7fff) << 1) | 1; + *tsb++ = u16 >> 8; + *tsb++ = u16; + u16 = ((ts & 0x7fff) << 1) | 1; + *tsb++ = u16 >> 8; + *tsb++ = u16; + + /* Write DTS */ + + ts = av_rescale_q(pkt->pkt_dts, AV_TIME_BASE_Q, mpeg_tc); + *tsb++ = (((ts >> 30) & 7) << 1) | 1; + u16 = (((ts >> 15) & 0x7fff) << 1) | 1; + *tsb++ = u16 >> 8; + *tsb++ = u16; + u16 = ((ts & 0x7fff) << 1) | 1; + *tsb++ = u16 >> 8; + *tsb++ = u16; + } else { + *tsb++ = 0; /* Pointer field for tables */ + } + } + + /* Fill rest of TS packet with payload */ + + src = pkt_payload(pkt) + tms->tms_offset; + memcpy(tsb, src, tsrem); + + tms->tms_offset += tsrem; + + if(dumppkt) { + int i; + for(i = 0; i < 188; i++) + printf("%02x%c", tsb0[i], " \n"[i & 0xf]); + printf("\n"); + } + + assert(tsb0 + 188 == tsb + tsrem); + return 0; +} + + +/* + * + */ +int +ts_mux_packet(th_muxer_t *tm, int64_t pcr, uint8_t *outbuf, int maxblocks) +{ + th_muxstream_t *tms, *t; + th_pkt_t *pkt; + int rem, i; + int64_t pcr1; + + LIST_FOREACH(tms, &tm->tm_active_streams, tms_muxer_link) { + if(tms->tms_nextblock < pcr) + tms->tms_nextblock = pcr; + + if(tms->tms_curpkt != NULL) + pkt_load(tms->tms_curpkt); + } + + for(i = 0; i < maxblocks; i++) { + + /* Find the stream with the lowest/closest time for next scheduled + transport stream block */ + + tms = NULL; + LIST_FOREACH(t, &tm->tm_active_streams, tms_muxer_link) + if(tms == NULL || t->tms_nextblock < tms->tms_nextblock) + tms = t; + + if(tms == NULL) + break; /* No active streams, cannot do anything */ + + + pkt = tms->tms_curpkt; + + /* Do we need to send a new PCR update? */ + + if(tms->tms_dopcr && tms->tms_nextpcr <= pcr) { + pcr1 = pcr + 200000; + tms->tms_nextpcr = pcr + 20000; + } else { + pcr1 = AV_NOPTS_VALUE; + } + + /* Generate a transport stream packet */ + + if(ts_make_pkt(tm, tms, outbuf, pcr1) < 0) { + /* Packet buffer no longer exist, we need to seek again */ + return -1; + } + outbuf += 188; + + rem = pkt_len(pkt) - tms->tms_offset; + if(rem == 0) { + /* End of frame, find next */ + while(1) { + pkt = pkt->pkt_on_stream_queue ? + TAILQ_NEXT(pkt, pkt_queue_link) : NULL; + if(pkt != NULL) { + /* Ok, reset counters */ + tms_stream_set_active(tm, tms, pkt, pcr); + pkt_load(tms->tms_curpkt); + + } else { + /* This stream cannot contribute, move it to stale list */ + tms_stream_set_stale(tm, tms, pcr); + } + break; + } + continue; + } + tms->tms_nextblock += tms->tms_block_interval; + tms->tms_blockcnt++; +#if 0 + if(tms->tms_stream) + printf("%-10s %lld [seg: %d] pcr=%lld dl=%lld nxt=%lld off=%d d=%lld\n", + htstvstreamtype2txt(tms->tms_stream->st_type), + pkt->pkt_dts, tms->tms_blockcnt, pcr, tms->tms_dl, + tms->tms_nextblock, tms->tms_offset, + tms->tms_nextblock - pcr); +#endif + } + return i; +} + +/* + * + */ +void +tm_gen_pat_pmt(th_muxer_t *tm, int64_t pcr) +{ + th_subscription_t *s = tm->tm_subscription; + th_transport_t *t = s->ths_transport; + th_pkt_t *pkt; + + pkt = pkt_alloc(NULL, 4096, pcr, pcr); + pkt->pkt_payloadlen = psi_build_pmt(tm, pkt_payload(pkt), 4096); + tms_stream_add_meta(tm, tm->tm_pmt, pkt); + pkt_deref(pkt); + + pkt = pkt_alloc(NULL, 4096, pcr, pcr); + pkt->pkt_payloadlen = psi_build_pat(t, pkt_payload(pkt), 4096); + tms_stream_add_meta(tm, tm->tm_pat, pkt); + pkt_deref(pkt); +} + + + +static void ts_gen_timer_callback(void *aux, int64_t now); + +/* + * + */ +void +ts_gen_packet(th_muxer_t *tm, int64_t clk) +{ + int64_t pcr, next; + th_muxstream_t *tms; + int r; + + dtimer_disarm(&tm->tm_timer); + + pcr = tm->tm_start_dts + clk - tm->tm_clockref; + + do { + if(pcr >= tm->tm_next_pat) { + tm->tm_next_pat = pcr + 100000; + tm_gen_pat_pmt(tm, pcr); + } + + r = ts_mux_packet(tm, pcr, tm->tm_packet, tm->tm_blocks_per_packet); + if(r == -1) { + ts_muxer_relock(tm, pcr); + return; + } + + tm->tm_callback(tm->tm_opaque, tm->tm_subscription, tm->tm_packet, r, pcr); + + /* Figure when next packet must be sent */ + next = INT64_MAX; + LIST_FOREACH(tms, &tm->tm_active_streams, tms_muxer_link) + if(tms->tms_nextblock < next) + next = tms->tms_nextblock; + + next = next - pcr; + } while(next < 2000); + + if(next > 100000) + next = 100000; /* We always want to send PAT/PMT at this interval */ + + dtimer_arm_hires(&tm->tm_timer, ts_gen_timer_callback, tm, clk + next); +} + +/* + * + */ +static void +ts_gen_timer_callback(void *aux, int64_t now) +{ + th_muxer_t *tm = aux; + ts_gen_packet(tm, now); +} + + +/* + * start demuxing + */ +static int +ts_muxer_start(th_muxer_t *tm) +{ + th_muxstream_t *tms; + th_pkt_t *f, *l, *pkt; + th_stream_t *st; + int64_t v; + + LIST_FOREACH(tms, &tm->tm_stopped_streams, tms_muxer_link) { + st = tms->tms_stream; + if(st == NULL) + continue; + + f = TAILQ_FIRST(&st->st_pktq); + l = TAILQ_LAST(&st->st_pktq, th_pkt_queue); + + if(f == NULL) + return -1; + + v = muxer_pkt_deadline(f, tms) - f->pkt_duration; + + if(tm->tm_start_dts < v) { + tm->tm_start_dts = v; + tms->tms_tmppkt = f; + continue; + } + + v = muxer_pkt_deadline(l, tms); + + if(tm->tm_start_dts > v) { + tm->tm_start_dts = v - l->pkt_duration; + tms->tms_tmppkt = l; + continue; + } + + tms->tms_tmppkt = NULL; + + while(f != NULL || l != NULL) { + + if(f != NULL) + f = TAILQ_NEXT(f, pkt_queue_link); + + if(f != NULL) { + v = muxer_pkt_deadline(f, tms); + if(tm->tm_start_dts >= v - f->pkt_duration && tm->tm_start_dts < v) { + tms->tms_tmppkt = f; + break; + } + } + if(l != NULL) + l = TAILQ_PREV(l, th_pkt_queue, pkt_queue_link); + + if(l != NULL) { + v = muxer_pkt_deadline(l, tms); + if(tm->tm_start_dts >= v - l->pkt_duration && tm->tm_start_dts < v) { + tms->tms_tmppkt = l; + break; + } + } + } + + if(tms->tms_tmppkt == NULL) + return -1; + } + + /* All streams have a lock */ + + tm->tm_status = TM_PLAY; + + while((tms = LIST_FIRST(&tm->tm_stopped_streams)) != NULL) { + st = tms->tms_stream; + pkt = tms->tms_tmppkt; + + if(st == NULL) { + LIST_REMOVE(tms, tms_muxer_link); + LIST_INSERT_HEAD(&tm->tm_active_streams, tms, tms_muxer_link); + } else { + tms_stream_set_active(tm, tms, pkt, tm->tm_start_dts); + } + } + + tm->tm_clockref = getclock_hires(); + ts_gen_packet(tm, tm->tm_clockref); + return 0; +} + + +/* + * + */ +static void +ts_muxer_reinit_stream(th_muxer_t *tm, th_muxstream_t *tms) +{ + tms->tms_nextblock = INT64_MAX; + LIST_REMOVE(tms, tms_muxer_link); + LIST_INSERT_HEAD(&tm->tm_stopped_streams, tms, tms_muxer_link); + tms_set_curpkt(tms, NULL); +} + +/* + * + */ +static void +ts_muxer_relock(th_muxer_t *tm, uint64_t pcr) +{ + th_muxstream_t *tms; + + while((tms = LIST_FIRST(&tm->tm_active_streams)) != NULL) + ts_muxer_reinit_stream(tm, tms); + + while((tms = LIST_FIRST(&tm->tm_stale_streams)) != NULL) + ts_muxer_reinit_stream(tm, tms); + + tm->tm_start_dts = pcr; + tm->tm_status = TM_WAITING_FOR_LOCK; + ts_muxer_start(tm); +} + + +/* + * pause playback + */ +void +ts_muxer_pause(th_muxer_t *tm) +{ + tm->tm_pauseref = getclock_hires(); + + dtimer_disarm(&tm->tm_timer); + tm->tm_status = TM_PAUSE; +} + + + + +/* + * playback start + */ +void +ts_muxer_play(th_muxer_t *tm, int64_t toffset) +{ + int64_t dts = 0, t; + th_muxstream_t *tms; + th_stream_t *st; + int64_t clk; + + switch(tm->tm_status) { + case TM_IDLE: + case TM_WAITING_FOR_LOCK: + + toffset = 0; + break; + + case TM_PLAY: + if(toffset == AV_NOPTS_VALUE) + return; + + toffset = 0; + return; /* XXX */ + + case TM_PAUSE: + if(toffset == AV_NOPTS_VALUE) { + /* Just continue stream, adjust clock reference with the amount + of time we was paused */ + clk = getclock_hires(); + t = clk - tm->tm_pauseref; + + tm->tm_clockref += t; + ts_gen_packet(tm, clk); + return; + } + break; + } + + assert(LIST_FIRST(&tm->tm_active_streams) == NULL); + assert(LIST_FIRST(&tm->tm_stale_streams) == NULL); + + LIST_FOREACH(tms, &tm->tm_stopped_streams, tms_muxer_link) { + if((st = tms->tms_stream) == NULL) + continue; + if(st->st_last_dts > dts) + dts = st->st_last_dts; + } + + dts -= toffset; + if(dts < 0) + dts = 0; + + tm->tm_start_dts = dts; + tm->tm_status = TM_WAITING_FOR_LOCK; + + ts_muxer_start(tm); +} + + +/* + * Meta streams + */ +th_muxstream_t * +tm_create_meta_stream(th_muxer_t *tm, int pid) +{ + th_muxstream_t *tms; + + tms = calloc(1, sizeof(th_muxstream_t)); + tms->tms_muxer = tm; + LIST_INSERT_HEAD(&tm->tm_stopped_streams, tms, tms_muxer_link); + tms->tms_index = pid; + return tms; +} + + + +/* + * + */ +static void +ts_encode_new_packet(th_muxer_t *tm, th_stream_t *st, th_pkt_t *pkt) +{ + th_muxstream_t *tms; + int64_t pcr; + + pkt_store(pkt); /* need to keep packet around */ + + switch(tm->tm_status) { + case TM_IDLE: + break; + + case TM_WAITING_FOR_LOCK: + ts_muxer_start(tm); + break; + + case TM_PLAY: + LIST_FOREACH(tms, &tm->tm_stale_streams, tms_muxer_link) { + if(tms->tms_stream == st) { + pcr = tm->tm_start_dts + getclock_hires() - tm->tm_clockref; + tms_stream_set_active(tm, tms, pkt, pcr); + break; + } + } + break; + + case TM_PAUSE: + break; + } +} + + + + + +/* + * TS Muxer + */ +th_muxer_t * +ts_muxer_init(th_subscription_t *s, th_mux_output_t *cb, void *opaque, + int flags) +{ + th_transport_t *t = s->ths_transport; + th_stream_t *st; + th_muxer_t *tm; + th_muxstream_t *tms; + int pididx = 100; + uint32_t sc; + int dopcr = 0; + int offset; + + tm = calloc(1, sizeof(th_muxer_t)); + LIST_INSERT_HEAD(&t->tht_muxers, tm, tm_transport_link); + tm->tm_subscription = s; + + tm->tm_clockref = getclock_hires(); + tm->tm_blocks_per_packet = 7; + tm->tm_callback = cb; + tm->tm_opaque = opaque; + tm->tm_new_pkt = ts_encode_new_packet; + tm->tm_flags = flags; + + tm->tm_packet = malloc(188 * tm->tm_blocks_per_packet); + + pididx = 200; + + LIST_FOREACH(st, &t->tht_streams, st_link) { + dopcr = 0; + offset = 0; + switch(st->st_type) { + case HTSTV_MPEG2VIDEO: + sc = 0x1e0; + dopcr = 1; + break; + case HTSTV_MPEG2AUDIO: + sc = 0x1c0; + break; + case HTSTV_AC3: + sc = 0x1bd; + break; + case HTSTV_H264: + sc = 0x1e0; + dopcr = 1; + break; + default: + continue; + } + + tms = calloc(1, sizeof(th_muxstream_t)); + tms->tms_muxer = tm; + tms->tms_stream = st; + tms->tms_dopcr = dopcr; + tms->tms_mux_offset = offset; + + LIST_INSERT_HEAD(&tm->tm_stopped_streams, tms, tms_muxer_link); + LIST_INSERT_HEAD(&tm->tm_media_streams, tms, tms_muxer_media_link); + + tms->tms_index = pididx; + tms->tms_sc = sc; + pididx++; + } + + tm->tm_pat = tm_create_meta_stream(tm, 0); + tm->tm_pmt = tm_create_meta_stream(tm, 100); + return tm; +} + + +/* + * + */ +static void +tms_destroy(th_muxstream_t *tms) +{ + if(tms->tms_stream) + LIST_REMOVE(tms, tms_muxer_media_link); + + LIST_REMOVE(tms, tms_muxer_link); + tms_set_curpkt(tms, NULL); + memset(tms, 0xff, sizeof(th_muxstream_t)); + free(tms); +} + + +/* + * + */ +void +ts_muxer_deinit(th_muxer_t *tm) +{ + th_muxstream_t *tms; + + LIST_REMOVE(tm, tm_transport_link); + + dtimer_disarm(&tm->tm_timer); + + tms_destroy(tm->tm_pat); + tms_destroy(tm->tm_pmt); + + while((tms = LIST_FIRST(&tm->tm_media_streams)) != NULL) + tms_destroy(tms); + + free(tm->tm_packet); + free(tm); +} + diff --git a/tsmux.h b/tsmux.h new file mode 100644 index 00000000..c65eac4c --- /dev/null +++ b/tsmux.h @@ -0,0 +1,31 @@ +/* + * tvheadend, MPEG transport stream muxer + * 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 TSMUX_H +#define TSMUX_H + +th_muxer_t *ts_muxer_init(th_subscription_t *s, th_mux_output_t *cb, + void *opaque, int flags); + +void ts_muxer_deinit(th_muxer_t *tm); + +void ts_muxer_play(th_muxer_t *tm, int64_t toffset); + +void ts_muxer_pause(th_muxer_t *tm); + +#endif /* TSMUX_H */ diff --git a/tvhead.h b/tvhead.h index 75bec429..409d04b8 100644 --- a/tvhead.h +++ b/tvhead.h @@ -40,6 +40,20 @@ typedef enum { } th_commercial_advice_t; +/* + * Dispatch timer + */ +typedef void (dti_callback_t)(void *opaque, int64_t now); + +typedef struct dtimer { + LIST_ENTRY(dtimer) dti_link; + dti_callback_t *dti_callback; + void *dti_opaque; + int64_t dti_expire; +} dtimer_t; + + + /* * List / Queue header declarations */ @@ -55,11 +69,15 @@ 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); - +LIST_HEAD(th_stream_list, th_stream); +TAILQ_HEAD(th_pkt_queue, th_pkt); +LIST_HEAD(th_pkt_list, th_pkt); +LIST_HEAD(th_muxer_list, th_muxer); +LIST_HEAD(th_muxstream_list, th_muxstream); extern time_t dispatch_clock; +extern int startupcounter; extern struct th_transport_list all_transports; extern struct th_channel_queue channels; extern struct pvr_rec_list pvrr_global_list; @@ -67,7 +85,7 @@ extern struct th_subscription_list subscriptions; struct th_transport; -struct th_pid; +struct th_stream; /* * Video4linux adapter @@ -98,17 +116,6 @@ typedef struct th_v4l_adapter { uint16_t tva_packet_len; int tva_lenlock; - struct { - void *tva_pes_packet; - uint16_t tva_pes_packet_len; - uint16_t tva_pes_packet_pos; - - void *tva_parser; - void *tva_ctx; - int tva_cc; - } tva_streams[2]; - - } th_v4l_adapter_t; @@ -143,7 +150,7 @@ typedef struct th_dvb_mux_instance { tdmi_state_t tdmi_state; - void *tdmi_initial_scan_timer; + dtimer_t tdmi_initial_scan_timer; const char *tdmi_status; time_t tdmi_got_adapter; @@ -156,6 +163,7 @@ typedef struct th_dvb_mux_instance { */ typedef struct th_dvb_table { LIST_ENTRY(th_dvb_table) tdt_link; + char *tdt_name; void *tdt_handle; struct th_dvb_mux_instance *tdt_tdmi; int tdt_count; /* times seen */ @@ -213,6 +221,9 @@ typedef struct th_dvb_adapter { struct th_transport_list tda_transports; /* Currently bound transports */ + dtimer_t tda_fec_monitor_timer; + dtimer_t tda_mux_scanner_timer; + } th_dvb_adapter_t; @@ -224,26 +235,68 @@ typedef struct th_dvb_adapter { * Section callback, called when a PSI table is fully received */ typedef void (pid_section_callback_t)(struct th_transport *t, - struct th_pid *pi, + struct th_stream *pi, uint8_t *section, int section_len); /* - * A PID in an MPEG transport stream is represented by this struct + * Stream, one media component for a transport */ -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? */ +typedef struct th_stream { + LIST_ENTRY(th_stream) st_link; + uint16_t st_pid; + uint8_t st_cc; /* Last CC */ + uint8_t st_cc_valid; /* Is CC valid at all? */ - tv_streamtype_t tp_type; - int tp_demuxer_fd; - int tp_index; + tv_streamtype_t st_type; + int st_demuxer_fd; + int st_index; + int st_peak_presentation_delay; /* Max seen diff. of DTS and PTS */ - struct psi_section *tp_section; - pid_section_callback_t *tp_got_section; -} th_pid_t; + struct psi_section *st_section; + pid_section_callback_t *st_got_section; + /* For transport stream packet reassembly */ + + uint8_t *st_buffer; + int st_buffer_ptr; + int st_buffer_size; + int st_buffer_errors; /* Errors accumulated for this packet */ + + /* DTS generator */ + + int32_t st_dts_u; /* upper bits (auto generated) */ + int64_t st_dts; + + /* Codec */ + + struct AVCodecContext *st_ctx; + struct AVCodecParserContext *st_parser; + + /* All packets currently hanging on to us */ + + struct th_pkt_list st_packets; + + /* Temporary frame store for calculating DTS */ + + struct th_pkt_queue st_dtsq; + int st_dtsq_len; + int64_t st_last_dts; + + /* Temporary frame store for calculating PTS */ + + struct th_pkt_queue st_ptsq; + int st_ptsq_len; + + /* Temporary frame store for calculating duration */ + + struct th_pkt_queue st_durationq; + int st_duration; + + /* Final frame store */ + + struct th_pkt_queue st_pktq; + +} th_stream_t; /* @@ -274,7 +327,9 @@ typedef struct th_transport { decoder */ int tht_prio; - struct th_pid_list tht_pids; + struct th_stream_list tht_streams; + th_stream_t *tht_video; + th_stream_t *tht_audio; uint16_t tht_pcr_pid; uint16_t tht_dvb_network_id; @@ -288,7 +343,8 @@ typedef struct th_transport { int tht_cc_error_log_limiter; int tht_rate_error_log_limiter; - + int64_t tht_dts_start; + LIST_ENTRY(th_transport) tht_adapter_link; LIST_ENTRY(th_transport) tht_channel_link; @@ -296,6 +352,13 @@ typedef struct th_transport { LIST_HEAD(, th_subscription) tht_subscriptions; + + struct th_muxer_list tht_muxers; /* muxers */ + + /* + * Per source type structs + */ + union { struct { @@ -341,6 +404,154 @@ typedef struct th_transport { #define tht_iptv_fd u.iptv.fd #define tht_iptv_mode u.iptv.mode + +/* + * Storage + */ +typedef struct th_storage { + unsigned int ts_offset; + unsigned int ts_refcount; + int ts_fd; + char *ts_filename; +} th_storage_t; + +/* + * A packet + */ + +#define PKT_I_FRAME 1 +#define PKT_P_FRAME 2 +#define PKT_B_FRAME 3 + + +typedef struct th_pkt { + TAILQ_ENTRY(th_pkt) pkt_queue_link; + uint8_t pkt_on_stream_queue; + uint8_t pkt_frametype; + uint8_t pkt_commercial; + + th_stream_t *pkt_stream; + + int64_t pkt_dts; + int64_t pkt_pts; + int pkt_duration; + int pkt_refcount; + + + th_storage_t *pkt_storage; + TAILQ_ENTRY(th_pkt) pkt_disk_link; + int pkt_storage_offset; + + uint8_t *pkt_payload; + int pkt_payloadlen; + TAILQ_ENTRY(th_pkt) pkt_mem_link; +} th_pkt_t; + + +/* + * A mux stream reader + */ +typedef struct th_muxstream { + + LIST_ENTRY(th_muxstream) tms_muxer_link; + struct th_muxer *tms_muxer; + + th_pkt_t *tms_curpkt; + + int tms_offset; /* offset in current packet */ + + th_stream_t *tms_stream; + LIST_ENTRY(th_muxstream) tms_muxer_media_link; + + int64_t tms_nextblock; /* Time for delivery of next block */ + int tms_block_interval; + int tms_block_rate; + + int tms_mux_offset; + + int tms_index; /* Used as PID or whatever */ + + th_pkt_t *tms_tmppkt; /* temporary pkt pointer during lock phaze */ + + /* MPEG TS multiplex stuff */ + + int tms_sc; /* start code */ + int tms_cc; + int tms_dopcr; + int64_t tms_nextpcr; + + /* Memebers used when running with ffmpeg */ + + struct AVStream *tms_avstream; + int tms_decoded; + + int tms_blockcnt; + int64_t tms_dl; + int64_t tms_staletime; + +} th_muxstream_t; + + +/* + * + */ + +struct th_subscription; + +typedef void (th_mux_output_t)(void *opaque, struct th_subscription *s, + uint8_t *pkt, int blocks, int64_t pcr); + + +typedef void (th_mux_newpkt_t)(struct th_muxer *tm, th_stream_t *st, + th_pkt_t *pkt); + +typedef struct th_muxer { + + th_mux_newpkt_t *tm_new_pkt; + + LIST_ENTRY(th_muxer) tm_transport_link; + + int64_t tm_clockref; /* Base clock ref */ + int64_t tm_pauseref; /* Time when we were paused */ + + int tm_flags; +#define TM_HTSCLIENTMODE 0x1 + + int64_t tm_start_dts; + int64_t tm_next_pat; + + struct th_muxstream_list tm_media_streams; + + struct th_muxstream_list tm_active_streams; + struct th_muxstream_list tm_stale_streams; + struct th_muxstream_list tm_stopped_streams; + + uint8_t *tm_packet; + int tm_blocks_per_packet; + + struct th_subscription *tm_subscription; + + th_mux_output_t *tm_callback; + void *tm_opaque; + + dtimer_t tm_timer; + + enum { + TM_IDLE, + TM_WAITING_FOR_LOCK, + TM_PLAY, + TM_PAUSE, + } tm_status; + + th_muxstream_t *tm_pat; + th_muxstream_t *tm_pmt; + + struct AVFormatContext *tm_avfctx; +} th_muxer_t; + + + + /* * Teletext */ @@ -393,6 +604,16 @@ typedef struct th_channel { /* * Subscription */ + +typedef enum { + TRANSPORT_AVAILABLE, + TRANSPORT_UNAVAILABLE, +} subscription_event_t; + +typedef void (subscription_callback_t)(struct th_subscription *s, + subscription_event_t event, + void *opaque); + typedef struct th_subscription { LIST_ENTRY(th_subscription) ths_global_link; int ths_weight; @@ -407,20 +628,13 @@ 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, th_pid_t *pi, - int64_t pcr); - - void *ths_opaque; - - char *ths_pkt; - int ths_pkt_ptr; - char *ths_title; /* display title */ - time_t ths_start; /* time when subscription started */ - int ths_total_err; /* total errors during entire subscription */ + subscription_callback_t *ths_callback; + void *ths_opaque; + } th_subscription_t; @@ -453,79 +667,6 @@ typedef struct event { } event_t; - -/* - * PVR packet data - */ -typedef struct pvr_data { - TAILQ_ENTRY(pvr_data) link; - uint8_t *tsb; - th_pid_t pi; -} pvr_data_t; - - -/* - * PVR Internal recording status - */ -typedef enum { - PVR_REC_STOP, - PVR_REC_WAIT_SUBSCRIPTION, - PVR_REC_WAIT_FOR_START, - PVR_REC_WAIT_AUDIO_LOCK, - PVR_REC_WAIT_VIDEO_LOCK, - PVR_REC_RUNNING, - PVR_REC_COMMERCIAL, - -} pvrr_rec_status_t; - - -/* - * PVR recording session - */ -typedef struct pvr_rec { - - LIST_ENTRY(pvr_rec) pvrr_global_link; - - th_channel_t *pvrr_channel; - - time_t pvrr_start; - time_t pvrr_stop; - - char *pvrr_filename; /* May be null if we havent figured out a name - yet, this happens upon record start. - Notice that this is full path */ - char *pvrr_title; /* Title in UTF-8 */ - char *pvrr_desc; /* Description in UTF-8 */ - - char *pvrr_printname; /* Only ASCII chars, used for logging and such */ - char *pvrr_format; /* File format trailer */ - - char pvrr_status; /* defined in libhts/htstv.h */ - char pvrr_error; /* dito - but status returned from recorder */ - - pvrr_rec_status_t pvrr_rec_status; /* internal recording status */ - - TAILQ_HEAD(, pvr_data) pvrr_dq; - int pvrr_dq_len; - - pthread_mutex_t pvrr_dq_mutex; - pthread_cond_t pvrr_dq_cond; - - int pvrr_ref; - - void *pvrr_opaque; /* For write out code */ - - th_subscription_t *pvrr_s; - - pthread_t pvrr_ptid; - - void *pvrr_timer; - -} pvr_rec_t; - - - - config_entry_t *find_mux_config(const char *muxtype, const char *muxname); char *utf8toprintable(const char *in); char *utf8tofilename(const char *in); diff --git a/v4l.c b/v4l.c index 022417d0..8f156906 100644 --- a/v4l.c +++ b/v4l.c @@ -43,7 +43,7 @@ #include "channels.h" #include "dispatch.h" #include "transports.h" -#include "ts.h" +#include "pes.h" static struct th_v4l_adapter_list v4l_adapters; @@ -51,12 +51,6 @@ static void v4l_fd_callback(int events, void *opaque, int fd); static int v4l_setfreq(th_v4l_adapter_t *tva, int frequency); -static void v4l_pes_packet(th_v4l_adapter_t *tva, uint8_t *buf, int len, - int type); - -static void v4l_ts_generate(th_v4l_adapter_t *tva, uint8_t *ptr, int len, - int type, int64_t pts, int64_t dts); - static void v4l_add_adapter(const char *path); @@ -66,7 +60,7 @@ static void v4l_add_adapter(const char *path); * */ void -v4l_add_adapters(void) +v4l_init(void) { v4l_add_adapter("/dev/video0"); } @@ -91,8 +85,8 @@ v4l_configure_transport(th_transport_t *t, const char *muxname, t->tht_v4l_frequency = atoi(config_get_str_sub(&ce->ce_sub, "frequency", "0")); - ts_add_pid(t, 100, HTSTV_MPEG2VIDEO); - ts_add_pid(t, 101, HTSTV_MPEG2AUDIO); + t->tht_video = transport_add_stream(t, -1, HTSTV_MPEG2VIDEO); + t->tht_audio = transport_add_stream(t, -1, HTSTV_MPEG2AUDIO); snprintf(buf, sizeof(buf), "Analog: %s (%.2f MHz)", muxname, (float)t->tht_v4l_frequency / 1000000.0f); @@ -232,7 +226,6 @@ v4l_start_feed(th_transport_t *t, unsigned int weight) { th_v4l_adapter_t *tva, *cand = NULL; int w, fd; - AVCodec *c; LIST_FOREACH(tva, &v4l_adapters, tva_link) { w = transport_compute_weight(&tva->tva_transports); @@ -251,21 +244,6 @@ v4l_start_feed(th_transport_t *t, unsigned int weight) tva = cand; } - - if(tva->tva_streams[0].tva_ctx == NULL) { - c = avcodec_find_decoder(CODEC_ID_MPEG2VIDEO); - tva->tva_streams[0].tva_ctx = avcodec_alloc_context(); - avcodec_open(tva->tva_streams[0].tva_ctx, c); - tva->tva_streams[0].tva_parser = av_parser_init(CODEC_ID_MPEG2VIDEO); - } - - if(tva->tva_streams[1].tva_ctx == NULL) { - c = avcodec_find_decoder(CODEC_ID_MP2); - tva->tva_streams[1].tva_ctx = avcodec_alloc_context(); - avcodec_open(tva->tva_streams[1].tva_ctx, c); - tva->tva_streams[1].tva_parser = av_parser_init(CODEC_ID_MP2); - } - if(tva->tva_dispatch_handle == NULL) { fd = open(tva->tva_path, O_RDWR); if(fd == -1) @@ -297,9 +275,11 @@ static void v4l_fd_callback(int events, void *opaque, int fd) { th_v4l_adapter_t *tva = opaque; + th_transport_t *t; + th_stream_t *st; uint8_t buf[4000]; uint8_t *ptr, *pkt; - int len, s = 0, l, r; + int len, l, r; if(!(events & DISPATCH_READ)) return; @@ -308,6 +288,10 @@ v4l_fd_callback(int events, void *opaque, int fd) if(len < 1) return; + t = LIST_FIRST(&tva->tva_transports); + if(t == NULL) + return; + ptr = buf; while(len > 0) { @@ -320,272 +304,39 @@ v4l_fd_callback(int events, void *opaque, int fd) continue; case 0x000001e0: - s = 0; /* video */ + st = t->tht_video; break; case 0x000001c0: - s = 1; /* audio */ + st = t->tht_audio; break; } if(tva->tva_lenlock == 2) { - l = tva->tva_streams[s].tva_pes_packet_len; - pkt = realloc(tva->tva_streams[s].tva_pes_packet, l); - tva->tva_streams[s].tva_pes_packet = pkt; + l = st->st_buffer_size; + st->st_buffer = pkt = realloc(st->st_buffer, l); - r = l - tva->tva_streams[s].tva_pes_packet_pos; + r = l - st->st_buffer_ptr; if(r > len) r = len; - memcpy(pkt + tva->tva_streams[s].tva_pes_packet_pos, ptr, r); + memcpy(pkt + st->st_buffer_ptr, ptr, r); ptr += r; len -= r; - tva->tva_streams[s].tva_pes_packet_pos += r; - if(tva->tva_streams[s].tva_pes_packet_pos == l) { - v4l_pes_packet(tva, pkt, l, s); + st->st_buffer_ptr += r; + if(st->st_buffer_ptr == l) { + pes_packet_input(t, st, pkt, l); + st->st_buffer_size = 0; tva->tva_startcode = 0; } } else { - tva->tva_streams[s].tva_pes_packet_len = - tva->tva_streams[s].tva_pes_packet_len << 8 | *ptr; + st->st_buffer_size = st->st_buffer_size << 8 | *ptr; tva->tva_lenlock++; if(tva->tva_lenlock == 2) { - tva->tva_streams[s].tva_pes_packet_pos = 0; + st->st_buffer_ptr = 0; } ptr++; len--; } } } - - - - -#define getu32(b, l) ({ \ - uint32_t x = (b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]); \ - b+=4; \ - l-=4; \ - x; \ -}) - -#define getu16(b, l) ({ \ - uint16_t x = (b[0] << 8 | b[1]); \ - b+=2; \ - l-=2; \ - x; \ -}) - -#define getu8(b, l) ({ \ - uint8_t x = b[0]; \ - b+=1; \ - l-=1; \ - x; \ -}) - -#define getpts(b, l) ({ \ - int64_t _pts; \ - _pts = (int64_t)((getu8(b, l) >> 1) & 0x07) << 30; \ - _pts |= (int64_t)(getu16(b, l) >> 1) << 15; \ - _pts |= (int64_t)(getu16(b, l) >> 1); \ - _pts; \ -}) - - -/* - * - */ -static void -v4l_pes_packet(th_v4l_adapter_t *tva, uint8_t *buf, int len, int type) -{ - uint8_t flags, hlen, x; - int64_t dts = AV_NOPTS_VALUE, pts = AV_NOPTS_VALUE; - uint8_t *outbuf; - int outlen, rlen; - AVCodecParserContext *p; - - x = getu8(buf, len); - flags = getu8(buf, len); - hlen = getu8(buf, len); - - if(len < hlen) - return; - - if((x & 0xc0) != 0x80) - /* no MPEG 2 PES */ - return; - - if((flags & 0xc0) == 0xc0) { - if(hlen < 10) - return; - - pts = getpts(buf, len); - dts = getpts(buf, len); - - hlen -= 10; - } else if((flags & 0xc0) == 0x80) { - if(hlen < 5) - return; - - dts = pts = getpts(buf, len); - hlen -= 5; - } - - buf += hlen; - len -= hlen; - - p = tva->tva_streams[type].tva_parser; - - while(len > 0) { - - rlen = av_parser_parse(p, tva->tva_streams[type].tva_ctx, - &outbuf, &outlen, buf, len, - pts, dts); - - if(outlen) - v4l_ts_generate(tva, outbuf, outlen, type, pts, dts); - - buf += rlen; - len -= rlen; - } - -} - - - - - -/* - * - */ -static void -v4l_ts_generate(th_v4l_adapter_t *tva, uint8_t *ptr, int len, int type, - int64_t pts, int64_t dts) -{ - th_pid_t p; - uint32_t sc; - uint8_t tsb0[188]; - uint8_t *tsb, *src; - int64_t ts, pcr = AV_NOPTS_VALUE; - int hlen, tlen, slen, plen, cc, pad; - uint16_t u16; - th_transport_t *t; - - memset(&p, 0, sizeof(p)); - - - - if(type) { - sc = 0x1c0; - p.tp_type = HTSTV_MPEG2AUDIO; - p.tp_pid = 101; - - } else { - sc = 0x1e0; - p.tp_type = HTSTV_MPEG2VIDEO; - p.tp_pid = 100; - if(pts != AV_NOPTS_VALUE) - pcr = dts; - } - - tsb = tsb0; - - slen = len; - len += 13; - - *tsb++ = 0x47; - *tsb++ = p.tp_pid >> 8 | 0x40; /* payload unit start indicator */ - *tsb++ = p.tp_pid; - - cc = ++tva->tva_streams[type].tva_cc; - - *tsb++ = (cc & 0xf) | 0x10; - - *tsb++ = 0; - *tsb++ = 0; - *tsb++ = sc >> 8; - *tsb++ = sc; - - *tsb++ = len >> 8; - *tsb++ = len; - - *tsb++ = 0x80; /* MPEG2 */ - - if(pts == AV_NOPTS_VALUE || dts == AV_NOPTS_VALUE) { - *tsb++ = 0x00; /* no ts */ - *tsb++ = 0; /* hdrlen */ - hlen = 0; - } else { - - *tsb++ = 0xc0; /* pts & dts */ - *tsb++ = 10; /* pts & dts */ - hlen = 10; - - ts = pts; - *tsb++ = (((ts >> 30) & 7) << 1) | 1; - u16 = (((ts >> 15) & 0x7fff) << 1) | 1; - *tsb++ = u16 >> 8; - *tsb++ = u16; - u16 = ((ts & 0x7fff) << 1) | 1; - *tsb++ = u16 >> 8; - *tsb++ = u16; - - ts = dts; - *tsb++ = (((ts >> 30) & 7) << 1) | 1; - u16 = (((ts >> 15) & 0x7fff) << 1) | 1; - *tsb++ = u16 >> 8; - *tsb++ = u16; - u16 = ((ts & 0x7fff) << 1) | 1; - *tsb++ = u16 >> 8; - *tsb++ = u16; - } - - tlen = 188 - 4 - 4 - 2 - 3 - hlen; - - src = ptr; - - plen = FFMIN(tlen, slen); - - while(1) { - assert(plen != 0); - assert(slen > 0); - - memcpy(tsb, src, plen); - - - LIST_FOREACH(t, &tva->tva_transports, tht_adapter_link) { - ts_recv_tsb(t, p.tp_pid, tsb0, 0, pcr); - pcr = AV_NOPTS_VALUE; - } - - slen -= plen; - if(slen == 0) - break; - - src += plen; - - tsb = tsb0; - - *tsb++ = 0x47; - *tsb++ = p.tp_pid >> 8; - *tsb++ = p.tp_pid; - - tlen = 188 - 4; - - cc = ++tva->tva_streams[type].tva_cc; - - plen = FFMIN(tlen, slen); - if(plen == slen) { - tlen -= 2; - plen = FFMIN(tlen, slen); - pad = tlen - plen; - - *tsb++ = (cc & 0xf) | 0x30; - *tsb++ = pad + 1; - *tsb++ = 0; - tsb += pad; - - } else { - *tsb++ = (cc & 0xf) | 0x10; - } - } -} diff --git a/v4l.h b/v4l.h index a066d266..bee92bf2 100644 --- a/v4l.h +++ b/v4l.h @@ -19,7 +19,7 @@ #ifndef V4L_H_ #define V4L_H_ -void v4l_add_adapters(void); +void v4l_init(void); int v4l_configure_transport(th_transport_t *t, const char *muxname, const char *channel_name);