Rewrite all of the tvheadend packet internals

- Use an internal packet format instead of passing around TS packets
- Use an on-disk storage for pause/seek of live TV
- Fix RTSP interface
- Fix h264 recording
- Set correct DTS/PTS on all packets
This commit is contained in:
Andreas Öman 2007-10-27 07:40:30 +00:00
parent db5c773ad0
commit 6e6a2de48f
34 changed files with 3696 additions and 1808 deletions

View file

@ -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

362
buffer.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#define _XOPEN_SOURCE 500
#include <unistd.h>
#include <pthread.h>
#include <syslog.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

52
buffer.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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_ */

View file

@ -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) {

View file

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

View file

@ -20,6 +20,7 @@
#define DISPATCH_H
#include <sys/time.h>
#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 */

54
dvb.c
View file

@ -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 */

View file

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

7
epg.c
View file

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

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
@ -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) {

View file

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

View file

@ -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:

41
main.c
View file

@ -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 "<unknown>";
}
}

332
pes.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ffmpeg/avcodec.h>
#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);
}

25
pes.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 */

260
psi.c
View file

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
@ -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 "<unknown>";
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;
}

6
psi.h
View file

@ -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_ */

650
pvr.c
View file

@ -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;
}
}

67
pvr.h
View file

@ -19,9 +19,76 @@
#ifndef PVR_H
#define PVR_H
#include <ffmpeg/avformat.h>
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,

857
pvr_rec.c
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <iconv.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h>
#include <ffmpeg/avstring.h>
#include <libhts/htscfg.h>
#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;
}

209
rtp.c
View file

@ -37,185 +37,76 @@
#include <ffmpeg/avformat.h>
#include <ffmpeg/random.h>
#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);
}
}

16
rtp.h
View file

@ -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_ */

319
rtsp.c
View file

@ -17,6 +17,7 @@
*/
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
@ -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 <ffmpeg/avformat.h>
#include <ffmpeg/rtspcodes.h>
@ -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));

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <pthread.h>
#include <sys/types.h>
@ -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);
}

View file

@ -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 */

View file

@ -17,6 +17,7 @@
*/
#include <pthread.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
@ -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;
}

View file

@ -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 */

170
tsdemux.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include <libhts/htscfg.h>
#include <ffmpeg/avcodec.h>
#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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 */

864
tsmux.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include <libhts/htscfg.h>
#include <ffmpeg/avcodec.h>
#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);
}

31
tsmux.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 */

367
tvhead.h
View file

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

295
v4l.c
View file

@ -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;
}
}
}

2
v4l.h
View file

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