tvheadend/src/input/mpegts/mpegts_table.c
2014-11-12 10:35:33 +01:00

393 lines
9.3 KiB
C

/*
* MPEGTS table support
* Copyright (C) 2013 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 "tvheadend.h"
#include "input.h"
#include <assert.h>
void
mpegts_table_consistency_check ( mpegts_mux_t *mm )
{
#if ENABLE_TRACE
int i, c = 0;
mpegts_table_t *mt;
lock_assert(&mm->mm_tables_lock);
i = mm->mm_num_tables;
LIST_FOREACH(mt, &mm->mm_tables, mt_link)
c++;
if (i != c) {
tvherror("mpegts", "table: mux %p count inconsistency (num %d, list %d)", mm, i, c);
abort();
}
#endif
}
static void
mpegts_table_fastswitch ( mpegts_mux_t *mm )
{
char buf[256];
mpegts_table_t *mt;
if(mm->mm_scan_state != MM_SCAN_STATE_ACTIVE)
return;
pthread_mutex_lock(&mm->mm_tables_lock);
LIST_FOREACH(mt, &mm->mm_tables, mt_link) {
if (!(mt->mt_flags & MT_QUICKREQ) && !mt->mt_working)
continue;
if(!mt->mt_complete || mt->mt_working) {
pthread_mutex_unlock(&mm->mm_tables_lock);
return;
}
}
pthread_mutex_unlock(&mm->mm_tables_lock);
mpegts_mux_nice_name(mm, buf, sizeof(buf));
tvhinfo("mpegts", "%s scan complete", buf);
mpegts_mux_scan_done(mm, buf, 1);
}
void
mpegts_table_dispatch
( const uint8_t *sec, size_t r, void *aux )
{
int tid, len, ret;
mpegts_table_t *mt = aux;
int chkcrc = mt->mt_flags & MT_CRC;
if(mt->mt_destroyed)
return;
/* Table info */
tid = sec[0];
len = ((sec[1] & 0x0f) << 8) | sec[2];
if (tid == 0x72) { /* stuffing section */
if (len != r - 3) {
if (tvhlog_limit(&mt->mt_err_log, 10))
tvhwarn(mt->mt_name, "stuffing found with trailing data "
"(len %i, total %zi, errors %zi)",
len, r, mt->mt_err_log.count);
}
dvb_table_reset(mt);
return;
}
/* It seems some hardware (or is it the dvb API?) does not
honour the DMX_CHECK_CRC flag, so we check it again */
if(chkcrc && tvh_crc32(sec, r, 0xffffffff)) {
if (tvhlog_limit(&mt->mt_err_log, 10))
tvhwarn(mt->mt_name, "invalid checksum (len %zi, errors %zi)",
r, mt->mt_err_log.count);
return;
}
/* Not enough data */
if(len < r - 3) {
tvhtrace(mt->mt_name, "not enough data, %d < %d", (int)r, len);
return;
}
/* Check table mask */
if((tid & mt->mt_mask) != mt->mt_table)
return;
/* Strip trailing CRC */
if(chkcrc)
len -= 4;
/* Pass with tableid / len in data */
if (mt->mt_flags & MT_FULL)
ret = mt->mt_callback(mt, sec, len+3, tid);
/* Pass w/out tableid/len in data */
else
ret = mt->mt_callback(mt, sec+3, len, tid);
/* Good */
if(ret >= 0)
mt->mt_count++;
if(!ret && mt->mt_flags & (MT_QUICKREQ|MT_FASTSWITCH))
mpegts_table_fastswitch(mt->mt_mux);
}
void
mpegts_table_release_ ( mpegts_table_t *mt )
{
struct mpegts_table_state *st;
while ((st = RB_FIRST(&mt->mt_state))) {
RB_REMOVE(&mt->mt_state, st, link);
free(st);
}
tvhtrace("mpegts", "table: mux %p free %s %02X/%02X (%d) pid %04X (%d)",
mt->mt_mux, mt->mt_name, mt->mt_table, mt->mt_mask, mt->mt_table,
mt->mt_pid, mt->mt_pid);
if (mt->mt_bat)
dvb_bat_destroy(mt);
if (mt->mt_destroy)
mt->mt_destroy(mt);
free(mt->mt_name);
#if ENABLE_TRACE
/* poison */
memset(mt, 0xa5, sizeof(*mt));
#endif
free(mt);
}
static void
mpegts_table_destroy_ ( mpegts_table_t *mt )
{
mpegts_mux_t *mm = mt->mt_mux;
lock_assert(&mm->mm_tables_lock);
tvhtrace("mpegts", "table: mux %p destroy %s %02X/%02X (%d) pid %04X (%d)",
mm, mt->mt_name, mt->mt_table, mt->mt_mask, mt->mt_table,
mt->mt_pid, mt->mt_pid);
mpegts_table_consistency_check(mm);
mt->mt_destroyed = 1;
mt->mt_mux->mm_close_table(mt->mt_mux, mt);
mpegts_table_consistency_check(mm);
mpegts_table_release(mt);
}
void
mpegts_table_destroy ( mpegts_table_t *mt )
{
mpegts_mux_t *mm = mt->mt_mux;
pthread_mutex_lock(&mm->mm_tables_lock);
mpegts_table_destroy_(mt);
pthread_mutex_unlock(&mm->mm_tables_lock);
}
/**
* Determine table type
*/
int
mpegts_table_type ( mpegts_table_t *mt )
{
int type = 0;
if (mt->mt_flags & MT_FAST) type |= MPS_FTABLE;
if (mt->mt_flags & MT_SLOW) type |= MPS_TABLE;
if (mt->mt_flags & MT_RECORD) type |= MPS_STREAM;
if ((type & (MPS_FTABLE | MPS_TABLE)) == 0) type |= MPS_TABLE;
return type;
}
/**
* Add a new DVB table
*/
mpegts_table_t *
mpegts_table_add
( mpegts_mux_t *mm, int tableid, int mask,
mpegts_table_callback_t callback, void *opaque,
const char *name, int flags, int pid )
{
mpegts_table_t *mt;
int subscribe = 1;
/* Check for existing */
pthread_mutex_lock(&mm->mm_tables_lock);
mpegts_table_consistency_check(mm);
LIST_FOREACH(mt, &mm->mm_tables, mt_link) {
if (mt->mt_opaque != opaque)
continue;
if (mt->mt_pid < 0) {
if (strcmp(mt->mt_name, name))
continue;
mt->mt_callback = callback;
mt->mt_pid = pid;
mt->mt_table = tableid;
mm->mm_open_table(mm, mt, 1);
} else if (pid >= 0) {
if (mt->mt_pid != pid)
continue;
if (mt->mt_callback != callback)
continue;
} else {
if (strcmp(mt->mt_name, name))
continue;
if (!(flags & MT_SKIPSUBS) && !mt->mt_subscribed)
mm->mm_open_table(mm, mt, 1);
}
mpegts_table_consistency_check(mm);
pthread_mutex_unlock(&mm->mm_tables_lock);
return mt;
}
tvhtrace("mpegts", "table: mux %p add %s %02X/%02X (%d) pid %04X (%d)",
mm, name, tableid, mask, tableid, pid, pid);
/* Create */
mt = calloc(1, sizeof(mpegts_table_t));
mt->mt_arefcount = 1;
mt->mt_name = strdup(name);
mt->mt_callback = callback;
mt->mt_opaque = opaque;
mt->mt_pid = pid;
mt->mt_flags = flags & ~(MT_SKIPSUBS|MT_SCANSUBS);
mt->mt_table = tableid;
mt->mt_mask = mask;
mt->mt_mux = mm;
mt->mt_cc = -1;
/* Open table */
if (pid < 0) {
subscribe = 0;
} else if (flags & MT_SKIPSUBS) {
subscribe = 0;
} else if (flags & MT_SCANSUBS) {
if (mm->mm_scan_state == MM_SCAN_STATE_IDLE)
subscribe = 0;
}
mm->mm_open_table(mm, mt, subscribe);
mpegts_table_consistency_check(mm);
pthread_mutex_unlock(&mm->mm_tables_lock);
return mt;
}
/**
*
*/
void
mpegts_table_flush_all ( mpegts_mux_t *mm )
{
mpegts_table_t *mt;
descrambler_flush_tables(mm);
pthread_mutex_lock(&mm->mm_tables_lock);
mpegts_table_consistency_check(mm);
while ((mt = TAILQ_FIRST(&mm->mm_defer_tables))) {
TAILQ_REMOVE(&mm->mm_defer_tables, mt, mt_defer_link);
mt->mt_defer_cmd = 0;
mpegts_table_release(mt);
}
while ((mt = LIST_FIRST(&mm->mm_tables))) {
mt->mt_flags &= ~MT_DEFER; /* force destroy */
mpegts_table_consistency_check(mm);
mpegts_table_destroy_(mt);
mpegts_table_consistency_check(mm);
}
assert(mm->mm_num_tables == 0);
assert(TAILQ_FIRST(&mm->mm_defer_tables) == NULL);
assert(LIST_FIRST(&mm->mm_tables) == NULL);
pthread_mutex_unlock(&mm->mm_tables_lock);
}
/*
* Section assembly
*/
static int
mpegts_psi_section_reassemble0
( mpegts_psi_section_t *ps, const uint8_t *data,
int len, int start, int crc,
mpegts_psi_section_callback_t cb, void *opaque)
{
int excess, tsize;
if(start) {
// Payload unit start indicator
ps->ps_offset = 0;
ps->ps_lock = 1;
}
if(!ps->ps_lock)
return -1;
memcpy(ps->ps_data + ps->ps_offset, data, len);
ps->ps_offset += len;
if(ps->ps_offset < 3) {
/* We don't know the total length yet */
return len;
}
tsize = 3 + (((ps->ps_data[1] & 0xf) << 8) | ps->ps_data[2]);
if(ps->ps_offset < tsize)
return len; // Not there yet
excess = ps->ps_offset - tsize;
if(crc && tvh_crc32(ps->ps_data, tsize, 0xffffffff))
return -1;
ps->ps_offset = 0;
if (cb)
cb(ps->ps_data, tsize - (crc ? 4 : 0), opaque);
return len - excess;
}
/**
*
*/
void
mpegts_psi_section_reassemble
(mpegts_psi_section_t *ps, const uint8_t *tsb, int crc, int ccerr,
mpegts_psi_section_callback_t cb, void *opaque)
{
int off = tsb[3] & 0x20 ? tsb[4] + 5 : 4;
int pusi = tsb[1] & 0x40;
int r;
if (ccerr)
ps->ps_lock = 0;
if(off >= 188) {
ps->ps_lock = 0;
return;
}
if(pusi) {
int len = tsb[off++];
if(len > 0) {
if(len > 188 - off) {
ps->ps_lock = 0;
return;
}
mpegts_psi_section_reassemble0(ps, tsb + off, len, 0, crc, cb, opaque);
off += len;
}
}
while(off < 188) {
r = mpegts_psi_section_reassemble0(ps, tsb + off, 188 - off, pusi, crc,
cb, opaque);
if(r < 0) {
ps->ps_lock = 0;
break;
}
off += r;
pusi = 0;
}
}
/******************************************************************************
* Editor Configuration
*
* vim:sts=2:ts=2:sw=2:et
*****************************************************************************/