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);