diff --git a/Makefile b/Makefile index e153b38..123f17c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.7 2007/09/19 15:14:32 ajhseppa Exp $ +# $Id: Makefile,v 1.8 2007/09/24 13:03:38 ajhseppa Exp $ # Debugging on/off #IPTV_DEBUG = 1 @@ -57,7 +57,7 @@ endif ### The object files (add further files here): -OBJS = $(PLUGIN).o config.o setup.o device.o streamer.o protocoludp.o protocolhttp.o protocolfile.o pidfilter.o +OBJS = $(PLUGIN).o config.o setup.o device.o streamer.o protocoludp.o protocolhttp.o protocolfile.o sectionfilter.o ### The main target: diff --git a/device.c b/device.c index eecedf4..c4beded 100644 --- a/device.c +++ b/device.c @@ -3,15 +3,13 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: device.c,v 1.35 2007/09/22 17:37:57 rahrenbe Exp $ + * $Id: device.c,v 1.36 2007/09/24 13:03:38 ajhseppa Exp $ */ #include "common.h" #include "config.h" #include "device.h" -#define IPTV_FILTER_FILENAME "/tmp/vdr-iptv%d.filter%d" - #define IPTV_MAX_DEVICES 8 cIptvDevice * IptvDevices[IPTV_MAX_DEVICES]; @@ -34,21 +32,9 @@ cIptvDevice::cIptvDevice(unsigned int Index) pHttpProtocol = new cIptvProtocolHttp(); pFileProtocol = new cIptvProtocolFile(); pIptvStreamer = new cIptvStreamer(tsBuffer, &mutex); - // Initialize filters - memset(&filter, '\0', sizeof(filter)); - init_trans(&filter); - for (int i = 0; i < eMaxFilterCount; ++i) { - struct stat sb; - snprintf(filters[i].pipeName, sizeof(filters[i].pipeName), - IPTV_FILTER_FILENAME, deviceIndex, i); - stat(filters[i].pipeName, &sb); - if (S_ISFIFO(sb.st_mode)) - unlink(filters[i].pipeName); - memset(filters[i].pipeName, '\0', sizeof(filters[i].pipeName)); - filters[i].fifoDesc = -1; - filters[i].readDesc = -1; - filters[i].active = false; - } + // Initialize filter pointers + memset(&secfilters, '\0', sizeof(secfilters)); + StartSectionHandler(); } @@ -58,7 +44,7 @@ cIptvDevice::~cIptvDevice() delete pIptvStreamer; delete pUdpProtocol; delete tsBuffer; - // Iterate over all filters and clear their settings + // Destroy all filters for (int i = 0; i < eMaxFilterCount; ++i) DeleteFilter(i); } @@ -170,15 +156,9 @@ bool cIptvDevice::SetPid(cPidHandle *Handle, int Type, bool On) bool cIptvDevice::DeleteFilter(unsigned int Index) { debug("cIptvDevice::DeleteFilter(%d) Index=%d\n", deviceIndex, Index); - if ((Index < eMaxFilterCount) && filters[Index].active) { - close(filters[Index].fifoDesc); - close(filters[Index].readDesc); - unlink(filters[Index].pipeName); - memset(filters[Index].pipeName, '\0', sizeof(filters[Index].pipeName)); - filters[Index].fifoDesc = -1; - filters[Index].readDesc = -1; - filters[Index].active = false; - clear_trans_filt(&filter, Index); + if ((Index < eMaxFilterCount) && secfilters[Index]) { + delete secfilters[Index]; + secfilters[Index] = NULL; return true; } return false; @@ -188,33 +168,12 @@ int cIptvDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask) { // Search the next free filter slot for (unsigned int i = 0; i < eMaxFilterCount; ++i) { - if (!filters[i].active) { + if (!secfilters[i]) { debug("cIptvDevice::OpenFilter(%d): Pid=%d Tid=%02X Mask=%02X Index=%d\n", deviceIndex, Pid, Tid, Mask, i); - uint8_t mask[eMaxFilterMaskLen] = { 0 }; - uint8_t filt[eMaxFilterMaskLen] = { 0 }; - mask[0] = Mask; - filt[0] = Tid; - int err = set_trans_filt(&filter, i, Pid, &mask[0], &filt[0], 0); - if (err < 0) - error("Cannot set filter %d\n", i); - memset(filters[i].pipeName, '\0', sizeof(filters[i].pipeName)); - snprintf(filters[i].pipeName, sizeof(filters[i].pipeName), - IPTV_FILTER_FILENAME, deviceIndex, i); - struct stat sb; - stat(filters[i].pipeName, &sb); - if (S_ISFIFO(sb.st_mode)) - unlink(filters[i].pipeName); - err = mknod(filters[i].pipeName, 0644 | S_IFIFO, 0); - if (err < 0) { - char tmp[64]; - error("ERROR: mknod(): %s", strerror_r(errno, tmp, sizeof(tmp))); - break; - } - // Create descriptors - filters[i].fifoDesc = open(filters[i].pipeName, O_RDWR | O_NONBLOCK); - filters[i].readDesc = open(filters[i].pipeName, O_RDONLY | O_NONBLOCK); - filters[i].active = true; - return filters[i].readDesc; + + secfilters[i] = new cIptvSectionFilter(i, deviceIndex, Pid, Tid, + Mask); + return secfilters[i]->GetReadDesc(); } } // No free filter slot found @@ -225,7 +184,7 @@ bool cIptvDevice::CloseFilter(int Handle) { debug("cIptvDevice::CloseFilter(%d): %d\n", deviceIndex, Handle); for (unsigned int i = 0; i < eMaxFilterCount; ++i) { - if (Handle == filters[i].readDesc) + if (Handle == secfilters[i]->GetReadDesc()) return DeleteFilter(i); } return false; @@ -300,35 +259,10 @@ bool cIptvDevice::GetTSPacket(uchar *&Data) } isPacketDelivered = true; Data = p; - memcpy(filter.packet, p, sizeof(filter.packet)); - trans_filt(p, TS_SIZE, &filter); + // Run the data through all filters for (unsigned int i = 0; i < eMaxFilterCount; ++i) { - if (filters[i].active) { - section *filtered = get_filt_sec(&filter, i); - if (filtered->found) { - // Select on the fifo emptyness. Using null timeout to return immediately - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 0; - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(filters[i].fifoDesc, &rfds); - int retval = select(filters[i].fifoDesc + 1, &rfds, NULL, NULL, &tv); - // Check if error - if (retval < 0) { - char tmp[64]; - error("ERROR: select(): %s", strerror_r(errno, tmp, sizeof(tmp))); - DeleteFilter(i); - } - // There is no data in the fifo, more can be written - else if (!retval) { - int err = write(filters[i].fifoDesc, filtered->payload, filtered->length + 3); - if (err < 0) { - char tmp[64]; - error("ERROR: write(): %s", strerror_r(errno, tmp, sizeof(tmp))); - } - } - } + if (secfilters[i]) { + secfilters[i]->ProcessData(p); } } return true; diff --git a/device.h b/device.h index 66854d3..d0e5a3e 100644 --- a/device.h +++ b/device.h @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: device.h,v 1.15 2007/09/22 08:17:35 ajhseppa Exp $ + * $Id: device.h,v 1.16 2007/09/24 13:03:38 ajhseppa Exp $ */ #ifndef __IPTV_DEVICE_H @@ -16,15 +16,8 @@ #include "protocolfile.h" #include "streamer.h" -#include "pidfilter.h" +#include "sectionfilter.h" -struct filterInfo { - bool active; - int fifoDesc; - int readDesc; - char pipeName[128]; - int lastProvided; -}; class cIptvDevice : public cDevice { // static ones @@ -38,7 +31,6 @@ public: private: enum { eMaxFilterCount = 32, - eMaxFilterMaskLen = 16 }; unsigned int deviceIndex; bool isPacketDelivered; @@ -50,8 +42,7 @@ private: cIptvProtocolFile *pFileProtocol; cIptvStreamer *pIptvStreamer; cMutex mutex; - trans filter; - filterInfo filters[eMaxFilterCount]; + cIptvSectionFilter* secfilters[eMaxFilterCount]; // constructor & destructor public: diff --git a/sectionfilter.c b/sectionfilter.c new file mode 100644 index 0000000..c59fb7f --- /dev/null +++ b/sectionfilter.c @@ -0,0 +1,354 @@ +/* + * sectionfilter.c: IPTV plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: sectionfilter.c,v 1.1 2007/09/24 13:03:38 ajhseppa Exp $ + */ + +#include "sectionfilter.h" + +#define IPTV_FILTER_FILENAME "/tmp/vdr-iptv%d.filter%d" + +cIptvSectionFilter::cIptvSectionFilter(int Index, int devInd, + u_short Pid, u_char Tid, u_char Mask) + : pusi_seen(0), + feedcc(0), + doneq(0), + secbuf(NULL), + secbufp(0), + seclen(0), + tsfeedp(0), + crc_val(0), + pid(Pid), + id(Index) +{ + debug("cIptvSectionFilter::cIptvSectionFilter(%d)\n", Index); + + memset(secbuf_base, '\0', sizeof(secbuf_base)); + memset(filter_value, '\0', sizeof(filter_value)); + memset(filter_mask, '\0', sizeof(filter_mask)); + memset(filter_mode, '\0', sizeof(filter_mode)); + memset(maskandmode, '\0', sizeof(maskandmode)); + memset(maskandnotmode, '\0', sizeof(maskandnotmode)); + memset(pipeName, '\0', sizeof(pipeName)); + + SetPipeName(devInd); + + filter_value[0] = !Tid; + filter_mask[0] = Mask; + + uint8_t mask, mode, local_doneq = 0; + for (int i = 0; i < DVB_DEMUX_MASK_MAX; i++) { + mode = filter_mode[i]; + mask = filter_mask[i]; + maskandmode[i] = mask & mode; + local_doneq |= maskandnotmode[i] = mask & ~mode; + } + doneq = local_doneq ? 1 : 0; + + struct stat sb; + stat(pipeName, &sb); + if (S_ISFIFO(sb.st_mode)) + unlink(pipeName); + int err = mknod(pipeName, 0644 | S_IFIFO, 0); + if (err < 0) { + char tmp[64]; + error("ERROR: mknod(): %s", strerror_r(errno, tmp, sizeof(tmp))); + return; + } + + // Create descriptors + fifoDescriptor = open(pipeName, O_RDWR | O_NONBLOCK); + readDescriptor = open(pipeName, O_RDONLY | O_NONBLOCK); +} + +cIptvSectionFilter::~cIptvSectionFilter() +{ + debug("cIptvSectionFilter::~cIptvSectionfilter()\n"); + close(fifoDescriptor); + close(readDescriptor); + unlink(pipeName); + memset(pipeName, '\0', sizeof(pipeName)); + fifoDescriptor = -1; + readDescriptor = -1; +} + +void cIptvSectionFilter::SetPipeName(int deviceIndex) +{ + snprintf(pipeName, sizeof(pipeName), IPTV_FILTER_FILENAME, deviceIndex, id); +} + +int cIptvSectionFilter::GetReadDesc() +{ + return readDescriptor; +} + +inline uint16_t cIptvSectionFilter::section_length(const uint8_t *buf) +{ + return 3 + ((buf[1] & 0x0f) << 8) + buf[2]; +} + +inline uint16_t cIptvSectionFilter::ts_pid(const uint8_t *buf) +{ + return ((buf[1] & 0x1f) << 8) + buf[2]; +} + +inline uint8_t cIptvSectionFilter::payload(const uint8_t *tsp) +{ + if (!(tsp[3] & 0x10)) // no payload? + return 0; + + if (tsp[3] & 0x20) { // adaptation field? + if (tsp[4] > 183) // corrupted data? + return 0; + else + return 184 - 1 - tsp[4]; + } + + return 184; +} + +int cIptvSectionFilter::dvb_dmxdev_section_callback(const uint8_t *buffer1, size_t buffer1_len, + const uint8_t *buffer2, size_t buffer2_len, + enum dmx_success success) +{ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fifoDescriptor, &rfds); + int retval = select(fifoDescriptor + 1, &rfds, NULL, NULL, &tv); + + // Check if error + if (retval < 0) { + char tmp[64]; + error("ERROR: select(): %s", strerror_r(errno, tmp, sizeof(tmp))); + } + // There is no data in the fifo, more can be written + else if (!retval) { +#ifdef DEBUG_PRINTF + printf("id = %d, pid %d would now write %d data to buffer\n", id, pid, buffer1_len); + for (unsigned int i = 0; i < buffer1_len; ++i) { + printf("0x%X ", buffer1[i]); + } + printf("\n"); +#endif + + retval = write(fifoDescriptor, buffer1, buffer1_len); + + if (retval < 0) { + char tmp[64]; + error("ERROR: write(): %s", strerror_r(errno, tmp, sizeof(tmp))); + } + } +#ifdef DEBUG_PRINTF + else if (retval) { + printf("id %d pid %d data is already present\n", id, pid); + } +#endif + return 0; +} + + + +void cIptvSectionFilter::dvb_dmx_swfilter_section_new() +{ +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + if (secbufp < tsfeedp) { + int i, n = tsfeedp - secbufp; + + /* + * Section padding is done with 0xff bytes entirely. + * Due to speed reasons, we won't check all of them + * but just first and last. + */ + if (secbuf[0] != 0xff || secbuf[n - 1] != 0xff) { + printf("dvb_demux.c section ts padding loss: %d/%d\n", + n, tsfeedp); + printf("dvb_demux.c pad data:"); + for (i = 0; i < n; i++) + printf(" %02x", secbuf[i]); + printf("\n"); + } + } +#endif + + tsfeedp = secbufp = seclen = 0; + secbuf = secbuf_base; +} + + +int cIptvSectionFilter::dvb_dmx_swfilter_sectionfilter() +{ + uint8_t neq = 0; + int i; + + for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { + uint8_t local_xor = filter_value[i] ^ secbuf[i]; + + if (maskandmode[i] & local_xor) { +#ifdef DEBUG_PRINTF + printf("maskandmode discard\n"); +#endif + return 0; + } + + neq |= maskandnotmode[i] & local_xor; + } + + if (doneq && !neq) { +#ifdef DEBUG_PRINTF + printf("doneq discard, doneq = 0x%X, neq = 0x%X\n", doneq, !neq); +#endif + return 0; + } + + return dvb_dmxdev_section_callback(secbuf, seclen, NULL, 0, DMX_OK); +} + +inline int cIptvSectionFilter::dvb_dmx_swfilter_section_feed() +{ + if (dvb_dmx_swfilter_sectionfilter() < 0) + return -1; + + seclen = 0; + + return 0; +} + +int cIptvSectionFilter::dvb_dmx_swfilter_section_copy_dump(const uint8_t *buf, uint8_t len) +{ + uint16_t limit, seclen_local, n; + + if (tsfeedp >= DMX_MAX_SECFEED_SIZE) { + return 0; + } + if (tsfeedp + len > DMX_MAX_SECFEED_SIZE) { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printf("dvb_demux.c section buffer full loss: %d/%d\n", + tsfeedp + len - DMX_MAX_SECFEED_SIZE, + DMX_MAX_SECFEED_SIZE); +#endif + len = DMX_MAX_SECFEED_SIZE - tsfeedp; + } + + if (len <= 0) + return 0; + + memcpy(secbuf_base + tsfeedp, buf, len); + tsfeedp += len; + + /* + * Dump all the sections we can find in the data (Emard) + */ + limit = tsfeedp; + if (limit > DMX_MAX_SECFEED_SIZE) { + return -1; /* internal error should never happen */ + } + + /* to be sure always set secbuf */ + secbuf = secbuf_base + secbufp; + + for (n = 0; secbufp + 2 < limit; n++) { + seclen_local = section_length(secbuf); + if (seclen_local <= 0 || seclen_local > DMX_MAX_SECTION_SIZE + || seclen_local + secbufp > limit) { + return 0; + } +#ifdef DEBUG_PRINTF + printf("Non-mismatching seclen! %d, limit = %d\n", seclen_local, limit); +#endif + seclen = seclen_local; + crc_val = ~0; + /* dump [secbuf .. secbuf+seclen) */ + if (pusi_seen) { + dvb_dmx_swfilter_section_feed(); + } +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else + printf("dvb_demux.c pusi not seen, discarding section data\n"); +#endif + secbufp += seclen_local; /* secbufp and secbuf moving together is */ + secbuf += seclen_local; /* redundant but saves pointer arithmetic */ + } + + return 0; +} + + +void cIptvSectionFilter::ProcessData(const uint8_t* buf) +{ + if (buf[0] != 0x47) { + error("Not TS packet: 0x%X\n", buf[0]); + return; + } + + // Stop if not the PID this filter is looking for + if (ts_pid(buf) != pid) + return; + + uint8_t count = payload(buf); + + if (count == 0) /* count == 0 if no payload or out of range */ + return; + + uint8_t p = 188 - count; /* payload start */ + + uint8_t cc = buf[3] & 0x0f; + int ccok = ((feedcc + 1) & 0x0f) == cc; + feedcc = cc; + + int dc_i = 0; + if (buf[3] & 0x20) { + /* adaption field present, check for discontinuity_indicator */ + if ((buf[4] > 0) && (buf[5] & 0x80)) + dc_i = 1; + } + + + if (!ccok || dc_i) { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printf("dvb_demux.c discontinuity detected %d bytes lost\n", + count); + /* + * those bytes under sume circumstances will again be reported + * in the following dvb_dmx_swfilter_section_new + */ +#endif + /* + * Discontinuity detected. Reset pusi_seen = 0 to + * stop feeding of suspicious data until next PUSI=1 arrives + */ + pusi_seen = 0; + dvb_dmx_swfilter_section_new(); + } + + if (buf[1] & 0x40) { + /* PUSI=1 (is set), section boundary is here */ + if (count > 1 && buf[p] < count) { +#ifdef DEBUG_PRINTF + printf("Valid section\n"); +#endif + const uint8_t *before = &buf[p + 1]; + uint8_t before_len = buf[p]; + const uint8_t *after = &before[before_len]; + uint8_t after_len = count - 1 - before_len; + + dvb_dmx_swfilter_section_copy_dump(before, before_len); + /* before start of new section, set pusi_seen = 1 */ + pusi_seen = 1; + dvb_dmx_swfilter_section_new(); + dvb_dmx_swfilter_section_copy_dump(after, after_len); + } +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else if (count > 0) + printf("dvb_demux.c PUSI=1 but %d bytes lost\n", count); +#endif + } else { + /* PUSI=0 (is not set), no section boundary */ + dvb_dmx_swfilter_section_copy_dump(&buf[p], count); + } + +} diff --git a/sectionfilter.h b/sectionfilter.h new file mode 100644 index 0000000..62e4216 --- /dev/null +++ b/sectionfilter.h @@ -0,0 +1,117 @@ +/* + * sectionfilter.h: IPTV plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: sectionfilter.h,v 1.1 2007/09/24 13:03:39 ajhseppa Exp $ + */ + +#ifndef __IPTV_SECTIONFILTER_H +#define __IPTV_SECTIONFILTER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" + +#ifndef DMX_MAX_FILTER_SIZE +#define DMX_MAX_FILTER_SIZE 18 +#endif + +#ifndef DMX_MAX_SECTION_SIZE +#define DMX_MAX_SECTION_SIZE 4096 +#endif +#ifndef DMX_MAX_SECFEED_SIZE +#define DMX_MAX_SECFEED_SIZE (DMX_MAX_SECTION_SIZE + 188) +#endif + +#define DVB_DEMUX_MASK_MAX 18 + +class cIptvSectionFilter { + +private: + enum dmx_success { + DMX_OK = 0, /* Received Ok */ + DMX_LENGTH_ERROR, /* Incorrect length */ + DMX_OVERRUN_ERROR, /* Receiver ring buffer overrun */ + DMX_CRC_ERROR, /* Incorrect CRC */ + DMX_FRAME_ERROR, /* Frame alignment error */ + DMX_FIFO_ERROR, /* Receiver FIFO overrun */ + DMX_MISSED_ERROR /* Receiver missed packet */ + } ; + + int fifoDescriptor; + int readDescriptor; + + int pusi_seen; + int feedcc; + int doneq; + + uint8_t *secbuf; + uint8_t secbuf_base[DMX_MAX_SECFEED_SIZE]; + uint16_t secbufp; + uint16_t seclen; + uint16_t tsfeedp; + uint32_t crc_val; + + uint16_t pid; + int id; + + + uint8_t filter_value[DMX_MAX_FILTER_SIZE]; + uint8_t filter_mask[DMX_MAX_FILTER_SIZE]; + uint8_t filter_mode[DMX_MAX_FILTER_SIZE]; + + uint8_t maskandmode[DMX_MAX_FILTER_SIZE]; + uint8_t maskandnotmode[DMX_MAX_FILTER_SIZE]; + + char pipeName[128]; + + inline uint16_t section_length(const uint8_t *buf); + inline uint16_t ts_pid(const uint8_t *buf); + inline uint8_t payload(const uint8_t *tsp); + + int dvb_dmxdev_section_callback(const uint8_t *buffer1, + size_t buffer1_len, + const uint8_t *buffer2, + size_t buffer2_len, + enum dmx_success success); + + void dvb_dmx_swfilter_section_new(); + + int dvb_dmx_swfilter_sectionfilter(); + + inline int dvb_dmx_swfilter_section_feed(); + + int dvb_dmx_swfilter_section_copy_dump(const uint8_t *buf, + uint8_t len); + + int GetFifoDesc(); + void ClearPipeName(); + void SetPipeName(int deviceIndex); + +public: + // constructor & destructor + cIptvSectionFilter(int Index, int devInd, u_short Pid, u_char Tid, + u_char Mask); + + virtual ~cIptvSectionFilter(); + + void ProcessData(const uint8_t* buf); + + int GetReadDesc(); +}; + +#endif // __IPTV_SECTIONFILTER_H +