diff options
author | Lars Heer <l.heer@gmx.de> | 2013-09-18 14:47:42 +0200 |
---|---|---|
committer | Lars Heer <l.heer@gmx.de> | 2013-09-18 14:47:42 +0200 |
commit | c49649d7324c481a2a39145b77259eb58655df22 (patch) | |
tree | 52e6c3063d0608748c67e9658defdb2ffa4cfbd7 /mcast | |
parent | 58aa9d6ebc3bb122067c72b21af84e51aa655ad0 (diff) | |
download | vdr-plugin-mcli-c49649d7324c481a2a39145b77259eb58655df22.tar.gz vdr-plugin-mcli-c49649d7324c481a2a39145b77259eb58655df22.tar.bz2 |
added netcv2dvbip revision 188 from https://svn.baycom.de/repos/vdr-mcli-plugin/mcast/netcv2dvbip
Diffstat (limited to 'mcast')
28 files changed, 4532 insertions, 0 deletions
diff --git a/mcast/netcv2dvbip/.indent.pro b/mcast/netcv2dvbip/.indent.pro new file mode 100644 index 0000000..2faef85 --- /dev/null +++ b/mcast/netcv2dvbip/.indent.pro @@ -0,0 +1 @@ +-i8 -br -l0 -ce -npsl diff --git a/mcast/netcv2dvbip/README b/mcast/netcv2dvbip/README new file mode 100644 index 0000000..1880e48 --- /dev/null +++ b/mcast/netcv2dvbip/README @@ -0,0 +1,132 @@ +This is a tool for streaming DVB TS packets via udp multicasts on demand from the netceiver. +It listens for IGMP(v1/v2/v3) join/leave multicast group messages on the network and starts/stops +the corresponding streams accordingly. It uses a channels.conf file for channel information. +Each channel is mapped to one multicast group starting with 239.255.0.1 and so on. + +Written by: +Christian Cier-Zniewski <c.cier@gmx.de> +Project homepage: http://nanodev.nfshost.com/netcv2dvbip/ +SVN source code repository: https://svn.baycom.de/repos/vdr-mcli-plugin/mcast/ + +The software is based on source code from the following projects: + +IGMP component of vdr-streamdev-plugin +Frank Schmirler <vdrdev@schmirler.de> + +VDR +Klaus Schmidinger <kls@cadsoft.de> + +Netceiver mcli-lib +Baycom GmbH <info@baycom.de> + +----------------------------------------------------------- + +Quick Compile Howto (Linux): + +1) +svn co https://svn.baycom.de/repos/vdr-mcli-plugin/mcast/ + +2) +cd mcast + +3) +cd client +make +cd .. + +4) +cd netcv2dvbip +make + +5) +./netcv2dvbip + +------------------------------------------------------------ + +Notes: + + * Each UDP packet contains 7*188=1316 bytes ( 7 TS packets) + * channels.conf: only frontend paramters are needed. PIDs are + extracted from the PAT/PMT on demand. + * Windows: MS Loopback Adapter Driver has to be installed if you only + want to stream local multicasts. + Configure a static unused IP for this adapter and add a route for + 239.255.0.0/16 to this ip + eg.: route add 239.255.0.0 mask 255.255.0.0 10.11.12.13 + where 10.11.12.13 is the static IP address of the loopback + adapter. + Linux: specify the option "-b lo" and make sure that the + multicast flag is set on the lo device. + If not, type: ifconfig lo multicast + * Windows: netcv2dvb for Windows is compiled using Visual C++ 2008 Express Edition + Therefore it needs the Microsoft Visual C++ 2008 Runtime Redistributable to be installed. + +Known Issues: + + * Linux: netcv2dvbip make use of the multicast routing API to be + able to receive all IGMP (v1,v2) messages on the subnet without joining + all groups. If you have configured 200 channels, then at least + those 200 groups would have to be joined to receive the group + specific queries. IGMPv3 does not suffer this "problem", since + all reports are sent to the group 224.0.0.22. + So, if you are already using software that make use of this API, + netcv2dvbip will fail to start, because only one program can make + use of this API. + * Windows XP does not support MLDv2 messages, so the built-in MLD-Reporter of + libmcli is used in the Windows version of netcv2dvbip. + Windows Vista and Windows 7 already support MLDv2. + * IMPORTANT note for VLAN users: Windows does not support VLANs as Linux does. + So, if you already using a VLAN-enabled network for the Netceiver and Reel-Netclients + then you must use a LAN card which offers VLAN support in the drivers. + Eg.: most Intel-adapters support this ( I am using a EXPI9301CT) + +------------------------------------------------------------ + +Possible clients: + + * VLC [Linux, Windows] + * vdr-iptv-plugin [Linux] + * DVBLink for IPTV (http://www.dvblogic.com/) [Windows] + * Mediaportal with IPTV source filter (with EPG) [Windows] + * [...] + +------------------------------------------------------------ + +Command line Options: + + -b <interface on which to listen for IGMP mesages and to send streams to> + Default: first found running interface + + -p <port to send streams to> + Default: 12345 + + -i <interface with netceiver network> + Default: first found IPv6 interface + + -c <channels.conf file> + + -e include EIT packets in streams (EPG) + + -h help + + +ChangeLog: +========== +2009-??-?? version 1.0.0 + - initial release + +2010-06-03 version 1.1.0 + - bugfixes: * number of channels > 255 is now handled correctly + * PMT PIDs > 255 had a wrong entry in the PAT + - new features: * DVB-C and DVB-T support + * support for VDR-1.7.x channels.conf format + (older formats and ReelVDR format are still supported) + * playlist file generation (M3U) + * EPG support: new command line option "-e" activates + sending of EIT packets (PID 0x12) in the stream. +2010-06-17 version 1.1.1 * bugfix: port number was not set correctly in M3U file + * bugfix: high CPU load (select() timeout was not + set correctly) + * changed: streams now also use non-blocking sockets + * Windows only: activate built-in MLDv2 reporter only for + Windows XP diff --git a/mcast/netcv2dvbip/channels.conf b/mcast/netcv2dvbip/channels.conf new file mode 100644 index 0000000..520e108 --- /dev/null +++ b/mcast/netcv2dvbip/channels.conf @@ -0,0 +1,3 @@ +:Hauptsender +Das Erste;ARD:11836:hC34:S19.2E:27500:101:102=deu,103=2ch;106=deu:104:0:28106:1:1101:0 +ZDF;ZDFvision:11954:hC34:S19.2E:27500:110:120=deu,121=2ch;125=deu:130:0:28006:1:1079:0 diff --git a/mcast/netcv2dvbip/clist.c b/mcast/netcv2dvbip/clist.c new file mode 100644 index 0000000..1916e08 --- /dev/null +++ b/mcast/netcv2dvbip/clist.c @@ -0,0 +1,196 @@ +/* + * tools.c: Various tools + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: tools.c 2.3 2009/05/31 11:43:24 kls Exp $ + */ + +#include <stdlib.h> + +#include "clist.h" + + +// --- cListObject ----------------------------------------------------------- + +cListObject::cListObject(void) +{ + prev = next = NULL; +} + +cListObject::~cListObject() +{ +} + +void cListObject::Append(cListObject *Object) +{ + next = Object; + Object->prev = this; +} + +void cListObject::Insert(cListObject *Object) +{ + prev = Object; + Object->next = this; +} + +void cListObject::Unlink(void) +{ + if (next) + next->prev = prev; + if (prev) + prev->next = next; + next = prev = NULL; +} + +int cListObject::Index(void) const +{ + cListObject *p = prev; + int i = 0; + + while (p) { + i++; + p = p->prev; + } + return i; +} + +// --- cListBase ------------------------------------------------------------- + +cListBase::cListBase(void) +{ + objects = lastObject = NULL; + count = 0; +} + +cListBase::~cListBase() +{ + Clear(); +} + +void cListBase::Add(cListObject *Object, cListObject *After) +{ + if (After && After != lastObject) { + After->Next()->Insert(Object); + After->Append(Object); + } + else { + if (lastObject) + lastObject->Append(Object); + else + objects = Object; + lastObject = Object; + } + count++; +} + +void cListBase::Ins(cListObject *Object, cListObject *Before) +{ + if (Before && Before != objects) { + Before->Prev()->Append(Object); + Before->Insert(Object); + } + else { + if (objects) + objects->Insert(Object); + else + lastObject = Object; + objects = Object; + } + count++; +} + +void cListBase::Del(cListObject *Object, bool DeleteObject) +{ + if (Object == objects) + objects = Object->Next(); + if (Object == lastObject) + lastObject = Object->Prev(); + Object->Unlink(); + if (DeleteObject) + delete Object; + count--; +} + +void cListBase::Move(int From, int To) +{ + Move(Get(From), Get(To)); +} + +void cListBase::Move(cListObject *From, cListObject *To) +{ + if (From && To) { + if (From->Index() < To->Index()) + To = To->Next(); + if (From == objects) + objects = From->Next(); + if (From == lastObject) + lastObject = From->Prev(); + From->Unlink(); + if (To) { + if (To->Prev()) + To->Prev()->Append(From); + From->Append(To); + } + else { + lastObject->Append(From); + lastObject = From; + } + if (!From->Prev()) + objects = From; + } +} + +void cListBase::Clear(void) +{ + while (objects) { + cListObject *object = objects->Next(); + delete objects; + objects = object; + } + objects = lastObject = NULL; + count = 0; +} + +cListObject *cListBase::Get(int Index) const +{ + if (Index < 0) + return NULL; + cListObject *object = objects; + while (object && Index-- > 0) + object = object->Next(); + return object; +} + +static int CompareListObjects(const void *a, const void *b) +{ + const cListObject *la = *(const cListObject **)a; + const cListObject *lb = *(const cListObject **)b; + return la->Compare(*lb); +} + +void cListBase::Sort(void) +{ + int n = Count(); +#ifndef WIN32 + cListObject *a[n]; +#else + cListObject **a; + a = new cListObject*[n]; +#endif + cListObject *object = objects; + int i = 0; + while (object && i < n) { + a[i++] = object; + object = object->Next(); + } + qsort(a, n, sizeof(cListObject *), CompareListObjects); + objects = lastObject = NULL; + for (i = 0; i < n; i++) { + a[i]->Unlink(); + count--; + Add(a[i]); + } +} + diff --git a/mcast/netcv2dvbip/clist.h b/mcast/netcv2dvbip/clist.h new file mode 100644 index 0000000..d44da28 --- /dev/null +++ b/mcast/netcv2dvbip/clist.h @@ -0,0 +1,60 @@ +/* + * tools.h: Various tools + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: tools.h 2.2 2009/04/14 20:41:39 kls Exp $ + */ + +#ifndef __LIST_H +#define __LIST_H + +//#include "misc.h" + +class cListObject { +private: + cListObject *prev, *next; +public: + cListObject(void); + virtual ~cListObject(); + virtual int Compare(const cListObject &ListObject) const { return 0; } + ///< Must return 0 if this object is equal to ListObject, a positive value + ///< if it is "greater", and a negative value if it is "smaller". + void Append(cListObject *Object); + void Insert(cListObject *Object); + void Unlink(void); + int Index(void) const; + cListObject *Prev(void) const { return prev; } + cListObject *Next(void) const { return next; } + }; + +class cListBase { +protected: + cListObject *objects, *lastObject; + cListBase(void); + int count; +public: + virtual ~cListBase(); + void Add(cListObject *Object, cListObject *After = NULL); + void Ins(cListObject *Object, cListObject *Before = NULL); + void Del(cListObject *Object, bool DeleteObject = true); + virtual void Move(int From, int To); + void Move(cListObject *From, cListObject *To); + virtual void Clear(void); + cListObject *Get(int Index) const; + int Count(void) const { return count; } + void Sort(void); + }; + +template<class T> class cList : public cListBase { +public: + T *Get(int Index) const { return (T *)cListBase::Get(Index); } + T *First(void) const { return (T *)objects; } + T *Last(void) const { return (T *)lastObject; } + T *Prev(const T *object) const { return (T *)object->cListObject::Prev(); } // need to call cListObject's members to + T *Next(const T *object) const { return (T *)object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists" + }; + + +#endif //__LIST_H diff --git a/mcast/netcv2dvbip/crc32.c b/mcast/netcv2dvbip/crc32.c new file mode 120000 index 0000000..0bc6c1f --- /dev/null +++ b/mcast/netcv2dvbip/crc32.c @@ -0,0 +1 @@ +../common/crc32.c
\ No newline at end of file diff --git a/mcast/netcv2dvbip/dvbipstream.h b/mcast/netcv2dvbip/dvbipstream.h new file mode 100644 index 0000000..a374617 --- /dev/null +++ b/mcast/netcv2dvbip/dvbipstream.h @@ -0,0 +1,99 @@ +#ifndef _DVBIPSTREAM_H +#define _DVBIPSTREAM_H + +#include "headers.h" +#include "misc.h" + +#define MAXAPIDS 32 +#define MAXDPIDS 4 +#define MAXCAIDS 2 + +#define TS_PER_UDP 7 + +typedef struct +{ + int number; + char *name; + char *provider; + char *shortName; + int type; + unsigned int frequency; + int srate; + int coderateH; + int coderateL; + int guard; + int polarization; + int inversion; + int modulation; + int source; + int transmission; + int bandwidth; + int hierarchy; + int vpid; + int ppid; + int sid; + int rid; + int tid; + int tpid; + int nid; + int caids[MAXCAIDS]; + int NumCaids; + int apids[MAXAPIDS]; + int NumApids; + int dpids[MAXDPIDS]; + int NumDpids; + int eitpids[1]; + int NumEitpids; + int sdtpids[1]; + int NumSdtpids; +} channel_t; + + +typedef struct _stream_buffer_t +{ + struct _stream_buffer_t *next; + char data[TS_PER_UDP*188]; +} stream_buffer_t; + +typedef struct +{ + recv_info_t *r; + stream_buffer_t *free; + stream_buffer_t *head; + stream_buffer_t *tail; + stream_buffer_t *wr; + stream_buffer_t *rd; + int fill; + int si_state; + psi_buf_t psi; + channel_t *cdata; + int pmt_pid; + int es_pids[32]; + int es_pidnum; + int fta; + pthread_t t; + int stop; + int ts_cnt; + pthread_mutex_t lock_bf; + pthread_mutex_t lock_ts; +} stream_info_t; + +channel_t *read_channel_list (char *filename); + +int get_channel_num (void); +int get_channel_name (int n, char *str, int maxlen); +channel_t *get_channel_data (int n); + +// mcilink.c +void mcli_startup (void); +void* mcli_stream_setup (const int channum); +size_t mcli_stream_access (void* handle, char **buf); +size_t mcli_stream_part_access (void* handle, char **buf); +void mcli_stream_skip (void* handle); +int mcli_stream_stop (void* handle); + +// parse.c +int ParseLine (const char *s, channel_t * ch); + + +#endif diff --git a/mcast/netcv2dvbip/headers.h b/mcast/netcv2dvbip/headers.h new file mode 100644 index 0000000..5ffa21b --- /dev/null +++ b/mcast/netcv2dvbip/headers.h @@ -0,0 +1,33 @@ +/* + * (c) BayCom GmbH, http://www.baycom.de, info@baycom.de + * + * See the COPYING file for copyright information and + * how to reach the author. + * + */ + +#ifndef __HEADERS_H__ +#define __HEADERS_H__ + +#include "defs.h" +#include "version.h" +#include "list.h" +#include "satlists.h" +#include "mcast.h" +#include "input.h" +#include "recv_ccpp.h" +#include "recv_tv.h" +#include "tools.h" +#include "interfaces.h" +#include "mcast.h" +#include "mld.h" +#include "api_server.h" +#include "tca_handler.h" +#include "tra_handler.h" +#include "mld_reporter.h" +#include "ciparser.h" +#include "ci_handler.h" +#include "mmi_handler.h" +#include "crc32.h" +#include "siparser.h" +#endif diff --git a/mcast/netcv2dvbip/iface.c b/mcast/netcv2dvbip/iface.c new file mode 100644 index 0000000..2cfa679 --- /dev/null +++ b/mcast/netcv2dvbip/iface.c @@ -0,0 +1,262 @@ +#include "dvbipstream.h" +#include "iface.h" + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> + +#ifndef WIN32 +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#endif + +#ifdef WIN32 +#include <winsock2.h> +#include <iphlpapi.h> +#endif + + +#ifndef WIN32 +int get_iface_list(struct ifconf *ifconf) +{ + int sock, rval; + + sock = socket(AF_INET,SOCK_STREAM,0); + if(sock < 0) + { + perror("socket"); + return (-1); + } + + if((rval = ioctl(sock, SIOCGIFCONF , (char*) ifconf )) < 0 ) + perror("ioctl(SIOGIFCONF)"); + + close(sock); + + return rval; +} + +int get_iface_ipaddress(struct ifreq *ifreq) +{ + int sock, rval; + + sock = socket(AF_INET,SOCK_STREAM,0); + if(sock < 0) + { + perror("socket"); + return (-1); + } + + if((rval = ioctl(sock, SIOCGIFADDR , (char*) ifreq )) < 0 ) { + //perror("ioctl(SIOCGIFADDR)"); + } + close(sock); + + return rval; +} + +int get_iface_flags(struct ifreq *ifreq) +{ + int sock, rval; + + sock = socket(AF_INET,SOCK_STREAM,0); + if(sock < 0) + { + perror("socket"); + return (-1); + } + + if((rval = ioctl(sock, SIOCGIFFLAGS , (char*) ifreq )) < 0 ) + perror("ioctl(SIOCGIFFLAGS)"); + + close(sock); + + return rval; +} + +int discover_interfaces(iface_t iflist[]) +{ + struct ifreq ifreqs[MAXIF]; + struct ifconf ifconf; + int nifaces, i; + + memset(&ifconf,0,sizeof(ifconf)); + ifconf.ifc_buf = (char*) (ifreqs); + ifconf.ifc_len = sizeof(ifreqs); + + if(get_iface_list(&ifconf) < 0) + return 0; + + nifaces = ifconf.ifc_len/sizeof(struct ifreq); + + if (!quiet) + printf("Interfaces (count = %d):\n", nifaces); + + for(i = 0; i < nifaces; i++) + { + strncpy(iflist[i].name, ifreqs[i].ifr_name, IFNAMSIZ); + + u_char *addr; + if(get_iface_ipaddress(&ifreqs[i])<0) + continue; + addr = (u_char *) & (((struct sockaddr_in *)& + (ifreqs[i]).ifr_addr)->sin_addr); + if (!quiet) + printf("\t%i - %-10s : addr %d.%d.%d.%d\n",(i+1), + ifreqs[i].ifr_name,addr[0],addr[1],addr[2], + addr[3]); + iflist[i].ipaddr = ( (struct sockaddr_in *)& + (ifreqs[i]).ifr_addr)->sin_addr.s_addr; + + } + return nifaces; +} + +#endif + +#ifdef WIN32 + +#define WORKING_BUFFER_SIZE 15000 +#define MAX_TRIES 3 + +#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +#define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) + +/* Note: could also use malloc() and free() */ + +int discover_interfaces(iface_t iflist[]) +{ + if (!quiet) + printf("Interfaces:\n"); + /* Declare and initialize variables */ + + DWORD dwRetVal = 0; + + unsigned int anum = 0; + + // Set the flags to pass to GetAdaptersAddresses + ULONG flags = GAA_FLAG_INCLUDE_PREFIX; + + // default to IPv4 address family + ULONG family = AF_INET; + + LPVOID lpMsgBuf = NULL; + + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + ULONG outBufLen = 0; + ULONG Iterations = 0; + + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + + // Allocate a 15 KB buffer to start with. + outBufLen = WORKING_BUFFER_SIZE; + + do { + + pAddresses = (IP_ADAPTER_ADDRESSES *) MALLOC(outBufLen); + if (pAddresses == NULL) { + printf + ("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n"); + return 0; + } + + dwRetVal = + GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen); + + if (dwRetVal == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = NULL; + } else { + break; + } + + Iterations++; + + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES)); + + if (dwRetVal == NO_ERROR) { + // If successful, output some information from the data we received + pCurrAddresses = pAddresses; + while (pCurrAddresses) { + if (!quiet) { + printf("\tIfIndex (IPv4 interface): %u\n", + (unsigned int)pCurrAddresses->IfIndex); + printf("\tAdapter name: %s\n", pCurrAddresses->AdapterName); + } + strncpy(iflist[anum].name, pCurrAddresses->AdapterName, + IFNAMSIZ); + + pUnicast = pCurrAddresses->FirstUnicastAddress; + + if (pCurrAddresses->OperStatus == IfOperStatusUp) + { + if (pUnicast != NULL) { + if (!quiet) + printf("\tUnicast Address: %s\n", + inet_ntoa(((struct sockaddr_in*) + (pUnicast->Address.lpSockaddr))->sin_addr) ); + iflist[anum].ipaddr = ((struct sockaddr_in*) + (pUnicast->Address.lpSockaddr))->sin_addr.S_un.S_addr; + } else { + if (!quiet) + printf("\tNo Unicast Addresses\n"); + } + + if (!quiet) { +#ifndef __MINGW32__ + printf("\tDescription: %wS\n", pCurrAddresses->Description); + printf("\tFriendly name: %wS\n", + pCurrAddresses->FriendlyName); +#else + printf("\tDescription: %ls\n", pCurrAddresses->Description); + printf("\tFriendly name: %ls\n", + pCurrAddresses->FriendlyName); +#endif + + printf("\tMtu: %lu\n", pCurrAddresses->Mtu); + + printf("\n"); + } + + anum++; + } + pCurrAddresses = pCurrAddresses->Next; + } + } else { + printf("Call to GetAdaptersAddresses failed with error: %d\n", + (int)dwRetVal); + if (dwRetVal == ERROR_NO_DATA) + printf("\tNo addresses were found for the requested parameters\n"); + else { + + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + // Default language + (LPTSTR) & lpMsgBuf, 0, NULL)) { + printf("\tError: %s", (char *)lpMsgBuf); + LocalFree(lpMsgBuf); + if (pAddresses) + FREE(pAddresses); + return 0; + } + } + } + + if (pAddresses) { + FREE(pAddresses); + } + + return 1; +} + + +#endif + + diff --git a/mcast/netcv2dvbip/iface.h b/mcast/netcv2dvbip/iface.h new file mode 100644 index 0000000..23ce100 --- /dev/null +++ b/mcast/netcv2dvbip/iface.h @@ -0,0 +1,22 @@ +#ifndef __IFACE_H +#define __IFACE_H + +#include "defs.h" + +#include "misc.h" + +// iface.c + +#define MAXIF 20 + +typedef struct +{ + char name[IFNAMSIZ]; + in_addr_t ipaddr; +} iface_t; + +int discover_interfaces(iface_t *iflist); + + +#endif + diff --git a/mcast/netcv2dvbip/igmp.c b/mcast/netcv2dvbip/igmp.c new file mode 100644 index 0000000..45b0e60 --- /dev/null +++ b/mcast/netcv2dvbip/igmp.c @@ -0,0 +1,890 @@ +#include "defs.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include <winsock2.h> +#ifndef __MINGW32__ +#include <mstcpip.h> +#else +#define SIO_RCVALL_IGMPMCAST _WSAIOW(IOC_VENDOR,3) +#endif +#else +#include <sys/socket.h> +#include <unistd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#ifdef APPLE +#include <netinet/ip_mroute.h> +#else +#define _LINUX_IN_H +#include <linux/mroute.h> +#endif +#endif + +#include <stdio.h> +#include <errno.h> + +#include "igmp.h" +#include "streamer.h" +#include "misc.h" + +#ifndef IGMP_ALL_HOSTS +#define IGMP_ALL_HOSTS htonl(0xE0000001L) +#endif +#ifndef IGMP_ALL_ROUTER +#define IGMP_ALL_ROUTER htonl(0xE0000002L) +#endif +#ifndef IGMP_ALL_V3REPORTS +#define IGMP_ALL_V3REPORTS htonl(0xE0000016L) +#endif + +// IGMP parameters according to RFC2236. All time values in seconds. +#define IGMP_ROBUSTNESS 2 +#define IGMP_QUERY_INTERVAL 125 +#define IGMP_QUERY_RESPONSE_INTERVAL 10 +#define IGMP_GROUP_MEMBERSHIP_INTERVAL \ + (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL) +#define IGMP_OTHER_QUERIER_PRESENT_INTERVAL \ + (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL / 2) +#define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL / 4) +#define IGMP_STARTUP_QUERY_COUNT IGMP_ROBUSTNESS +// This value is 1/10 sec. RFC default is 10. Reduced to minimum to +// free unused channels ASAP +#define IGMP_LAST_MEMBER_QUERY_INTERVAL_TS 1 +#define IGMP_LAST_MEMBER_QUERY_COUNT IGMP_ROBUSTNESS + + +// operations on struct timeval +#define TV_CMP(a, cmp, b) \ + (a.tv_sec == b.tv_sec ? a.tv_usec cmp b.tv_usec : a.tv_sec cmp b.tv_sec) +#define TV_SET(tv) (tv.tv_sec || tv.tv_usec) +#define TV_CLR(tv) memset(&tv, 0, sizeof(tv)) +#define TV_CPY(dst, src) memcpy(&dst, &src, sizeof(dst)) +#define TV_ADD(dst, ts) dst.tv_sec += ts / 10; \ + dst.tv_usec += (ts % 10) * 100000; \ + if (dst.tv_usec >= 1000000) { dst.tv_usec -= 1000000; dst.tv_sec++; } + +/* Taken from http://tools.ietf.org/html/rfc1071 */ +uint16_t inetChecksum(uint16_t *addr, int count) +{ + uint32_t sum = 0; + while (count > 1) { + sum += *addr++; + count -= 2; + } + + if( count > 0 ) + sum += * (uint8_t *) addr; + + while (sum>>16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + + +cMulticastGroup::cMulticastGroup(in_addr_t Group): + group(Group), + reporter(0), + stream(NULL) +{ + TV_CLR(timeout); + TV_CLR(v1timer); + TV_CLR(retransmit); +} + +cIgmpListener::cIgmpListener(cIgmpMain* igmpmain) + : cThread("IGMP listener") +{ + m_IgmpMain = igmpmain; +} + +bool cIgmpListener::Membership(in_addr_t mcaddr, bool Add) +{ + int rc = 0; + + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = mcaddr; + mreq.imr_interface.s_addr = m_bindaddr; + + rc = ::setsockopt(m_socket, IPPROTO_IP, + Add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, + (const char*) &mreq, sizeof(mreq)); + if (rc < 0) + { + printf("IGMP: unable to %s %s", Add ? "join" : "leave", \ + inet_ntoa(mreq.imr_multiaddr)); +#ifndef WIN32 + if (errno == ENOBUFS) + printf("consider increasing sys.net.ipv4.igmp_max_memberships"); + else +#endif + log_socket_error("IGMP setsockopt(IP_ADD/DROP_MEMBERSHIP)"); + return false; + } + + return true; +} + +void cIgmpListener::Destruct(void) +{ + int rc = 0; + in_addr_t defaultaddr = INADDR_ANY; + + Cancel(3); + + Membership(IGMP_ALL_HOSTS, false); + Membership(IGMP_ALL_ROUTER, false); + Membership(IGMP_ALL_V3REPORTS, false); + + rc = setsockopt(m_socket, IPPROTO_IP, IP_MULTICAST_IF, + (char *)&defaultaddr, sizeof(defaultaddr)); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(IP_MULTICAST_IF)"); + } + +#ifdef WIN32 + closesocket(m_socket); +#else + rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_DONE, NULL, 0); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(MRT_DONE)"); + } + close(m_socket); +#endif +} + +bool cIgmpListener::Initialize(iface_t bindif, int table) +{ + int rc = 0; +#ifndef WIN32 + int val = 0; +#endif + + m_bindaddr = bindif.ipaddr; + + m_socket = ::socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); + + if (m_socket < 0) + { + log_socket_error("IGMP socket()"); + return false; + } + +#ifdef WIN32 + //------------------------- + // Set the socket I/O mode: In this case FIONBIO + // enables or disables the blocking mode for the + // socket based on the numerical value of iMode. + // If iMode = 0, blocking is enabled; + // If iMode != 0, non-blocking mode is enabled. + u_long iMode = 1; + ioctlsocket(m_socket, FIONBIO, &iMode); +#else + int x=fcntl(m_socket,F_GETFL,0); // Get socket flags + fcntl(m_socket,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag +#endif + + +#ifndef WIN32 +#ifdef MRT_TABLE + if ( table ) + { + rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_TABLE, (void*)&table, sizeof(table) ); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(MRT_TABLE)"); + printf("IGMPv2 policy routing won't work! Please " + "enable multicast policy routing option in" + " the kernel configuration!\n"); + } + } +#endif + val = 1; + rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_INIT, + (void*)&val, sizeof(val) ); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(MRT_INIT)"); + printf("IGMPv2 won't work! Please enable multicast router " + "option in the kernel configuration!\n"); + } + + struct vifctl VifCtl; + VifCtl.vifc_vifi = 0; + VifCtl.vifc_flags = 0; // no tunnel, no source routing, register ? + VifCtl.vifc_threshold = 1;// Packet TTL must be at least 1 to pass them + VifCtl.vifc_rate_limit = 0;// Ratelimit + + VifCtl.vifc_lcl_addr.s_addr = m_bindaddr; + VifCtl.vifc_rmt_addr.s_addr = INADDR_ANY; + + rc = ::setsockopt( m_socket, IPPROTO_IP, MRT_ADD_VIF, + (char *)&VifCtl, sizeof( VifCtl ) ); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(MRT_ADD_VIF)"); + } + + +#endif +/* + rc = setsockopt(m_socket, IPPROTO_IP, IP_MULTICAST_IF, + (char *)&m_bindaddr, sizeof(m_bindaddr)); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(IP_MULTICAST_IF)"); + return false; + } +*/ + /* + val = 1; + rc = ::setsockopt( m_socket, SOL_SOCKET, SO_REUSEADDR, + (const char*)&val, sizeof(val) ); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(SO_REUSEADDR)"); + return false; + } + */ + + /* + val = 1; + rc = ::setsockopt( m_socket, SOL_IP, IP_ROUTER_ALERT, &val, + sizeof(val) ); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(IP_ROUTER_ALERT)"); + return false; + } + */ + +#ifdef WIN32 + //---------------------- + // Bind the socket. + // WIN32 requires this on a specific interface! + // INADDR_ANY is not allowed according to MSDN Lib for raw IGMP sockets. + sockaddr_in m_LocalAddr; + + m_LocalAddr.sin_family = AF_INET; + m_LocalAddr.sin_port = 0; + m_LocalAddr.sin_addr.s_addr = m_bindaddr; + + rc = ::bind( m_socket, (struct sockaddr*)&m_LocalAddr, + sizeof(m_LocalAddr) ); + if ( rc < 0 ) + { + log_socket_error("IGMP bind()"); + return false; + } + + int len = sizeof(struct sockaddr_in); + rc = ::getsockname( m_socket, (struct sockaddr*)&m_LocalAddr, + (socklen_t*) &len ); + if ( rc < 0 ) + { + log_socket_error("IGMP getsockname()"); + return false; + } +#else +#ifndef APPLE + // Normal bind() for this socket does not really work. the socket + // does not receive anything then. + // However, SO_BINDTODEVICE _does_ work. Maybe because of SOCK_RAW?! + rc = setsockopt(m_socket, SOL_SOCKET, SO_BINDTODEVICE, + (char *)&bindif.name, sizeof(bindif.name)); + if ( rc < 0 ) + { + log_socket_error("IGMP setsockopt(SO_BINDTODEVICE)"); + return false; + } +#endif +#endif + +#ifdef WIN32 + DWORD dwCtlCode = SIO_RCVALL_IGMPMCAST; + DWORD dwBytesSent = 0; + rc = WSAIoctl(m_socket, dwCtlCode, &dwCtlCode, sizeof(dwCtlCode), NULL, + 0, &dwBytesSent, NULL, NULL); + if ( rc == SOCKET_ERROR ) + { + log_socket_error("IGMP WSAIoctl()"); + return false; + } +#endif + + + if ( Membership(IGMP_ALL_HOSTS, true) + && Membership(IGMP_ALL_V3REPORTS, true) + && Membership(IGMP_ALL_ROUTER, true)) + { + Start(); + return true; + } + + return false; +} + +void cIgmpListener::Action() +{ + int recvlen; + struct pollfd p; + char recv_buf[8192]; + + + p.fd = m_socket; + p.events = POLLIN; + + while (Running()) + { + switch (poll(&p,1,1000)) { + case -1: +#ifndef WIN32 + if ( (errno != EINTR) && (errno != EWOULDBLOCK) ) +#else + int Rt; + Rt = WSAGetLastError(); + if ( (Rt != WSAEINTR) && (Rt != WSAEWOULDBLOCK) ) +#endif + log_socket_error( "IGMP poll()" ); + case 0: + continue; + + default: + if (!(p.revents&POLLIN)) + continue; + + recvlen = recv(m_socket, (char*)&recv_buf, + sizeof(recv_buf), 0); + if (recvlen < 0) + { +#ifndef WIN32 + if ( (errno == EINTR) || (errno == EWOULDBLOCK) ) +#else + int Rt; + Rt = WSAGetLastError(); + if ( (Rt == WSAEINTR) || (Rt == WSAEWOULDBLOCK) ) +#endif + continue; + log_socket_error("IGMP recv()"); + goto out; + } + Parse(recv_buf, recvlen); + break; + } + } +out:; +} + +void cIgmpListener::Parse(char* buffer, int len) +{ + char* buf = buffer; + in_addr_t groupaddr; + in_addr_t senderaddr; + + // Make sure we can at least extract the IP version + if (len < (int) sizeof(char)) + { + return; + } + // Check that IP version is IPV4 and that payload is IGMP + if ( (HI_BYTE(*buf) == 4) && ( *(buf+9) == IPPROTO_IGMP) ) + { + if (!quiet) { + printf("IGMP: SrcAddr: %d.%d.%d.%d -> ", + (unsigned char) *(buf+12), + (unsigned char) *(buf+13), + (unsigned char) *(buf+14), + (unsigned char) *(buf+15) ); + printf("DstAddr: %d.%d.%d.%d\n", + (unsigned char) *(buf+16), + (unsigned char) *(buf+17), + (unsigned char) *(buf+18), + (unsigned char) *(buf+19) ); + } + memcpy(&senderaddr, buf+12, 4); + + // skip rest of ip header and move to next to + // next protocol header + len -= LO_BYTE(*buf) * 4; + buf += LO_BYTE(*buf) * 4; + + uint16_t chksum = ((*(buf+3)<<8)&0xFF00 ) | (*(buf+2) & 0x00FF); + *(buf+2) = 0; + *(buf+3) = 0; + if (chksum != inetChecksum((uint16_t *)buf, len)) + { + printf("IGMP: INVALID CHECKSUM 0x%04x 0x%04x - " + "discarding packet.\n", chksum, + inetChecksum((uint16_t *)buf, len)); + return; + } + + if ( (len == 8) ) + { + if ( *buf == 0x11 ) + { + if (!quiet) { + printf("IGMP: Version: %s, Type: " + "Membership Query\n", + (*(buf+1) == 0 ) ? "1":"2"); + printf(" Group: %d.%d.%d.%d\n", + (unsigned char) *(buf+4), + (unsigned char) *(buf+5), + (unsigned char) *(buf+6), + (unsigned char) *(buf+7)); + } + memcpy(&groupaddr, buf+4, 4); + m_IgmpMain->ProcessIgmpQueryMessage( groupaddr, + senderaddr ); + } + else if ( *buf == 0x12 ) + { + if (!quiet) + printf("IGMP: Version: 1, Type: " + "Membership Report\n"); + } + else if (*buf == 0x16) + { + if (!quiet) + printf("IGMP: Version: 2, Type: " + "Membership Report\n"); + } + else if (*buf == 0x17) + { + if (!quiet) + printf("IGMP: Version: 2, Type: " + "Leave Group\n"); + } + + if( !quiet) + printf(" Group: %d.%d.%d.%d\n", + (unsigned char) *(buf+4), + (unsigned char) *(buf+5), + (unsigned char) *(buf+6), + (unsigned char) *(buf+7)); + memcpy(&groupaddr, buf+4, 4); + m_IgmpMain->ProcessIgmpReportMessage( (int) *buf, + groupaddr, senderaddr); + } + else if ( (*buf == 0x11) && (len > 8) ) + { + if (!quiet) { + printf("IGMP: Version: 3, Type: " + "Membership Query, Maximum " + "Response Time: %d\n",*(buf+1)); + printf(" Group: %d.%d.%d.%d\n", + (unsigned char) *(buf+4), + (unsigned char) *(buf+5), + (unsigned char) *(buf+6), + (unsigned char) *(buf+7)); + } + memcpy(&groupaddr, buf+4, 4); + m_IgmpMain->ProcessIgmpQueryMessage( groupaddr, + senderaddr ); + + } + else if ( (*buf == 0x22) && (len > 8) ) + { + unsigned short numrecords = ntohs((unsigned short)* + (buf+7)<<8|*(buf+6)); + if (!quiet) + printf("IGMP: Version: 3, Type: Membership " + "Report, Number of Records: %d\n", + numrecords); + + // Skip Header and move to records + buf += 8; + for(int i = 0; i<numrecords; i++) + { + unsigned short numsources = (unsigned short)* + (buf+3)<<8|*(buf+2); + if (!quiet) { + printf(" ---> Record No: %d, Type: " + "%d, Number of Sources: %d\n", + i+1, *buf, numsources); + printf(" Group: %d.%d.%d.%d\n", + (unsigned char) *(buf+4), + (unsigned char) *(buf+5), + (unsigned char) *(buf+6), + (unsigned char) *(buf+7)); + } + memcpy(&groupaddr, buf+4, 4); + m_IgmpMain->ProcessIgmpReportMessage( + (int) *buf, groupaddr, senderaddr); + + if (!quiet) for(int j = 0; j<numsources; j++) + { + printf(" Sources: " + "%d.%d.%d.%d\n", + (unsigned char) *(buf+j*4), + (unsigned char) *(buf+j*4+1), + (unsigned char) *(buf+j*4+2), + (unsigned char) *(buf+j*4+3)); + // move to next record: header bytes + + // aux data len + } + buf += 8 + *(buf+1) + (numsources *4); + } + } + else + { + printf("IGMP: Invalid packet\n"); + } + } + +} + +void cIgmpListener::IGMPSendQuery(in_addr_t Group, int Timeout) +{ + struct sockaddr_in dst; + uint16_t query[6]; + + dst.sin_family = AF_INET; + dst.sin_port = IPPROTO_IGMP; + dst.sin_addr.s_addr = Group; + + query[0] = ((Timeout * 10)&0xFF)<<8 | 0x11 ; // Membership Query + query[1] = 0x0000; // Checksum + if ( Group == IGMP_ALL_HOSTS ) + { // General Query + query[2] = 0x0000; + query[3] = 0x0000; + } + else + { // Group Query + memcpy(&(query[2]), &Group, 4); + } + query[4] = IGMP_QUERY_INTERVAL<<8 | 0x00 ; // S=0, QRV=0 + query[5] = 0x0000; // 0 sources + uint16_t chksum = inetChecksum( (uint16_t *) &query, sizeof(query)); + query[1] = chksum; + + for (int i = 0; i < 5 && ::sendto(m_socket, (const char*) &query, + sizeof(query), 0, (sockaddr*)&dst, sizeof(dst)) < 0 ; i++) + { +#ifndef WIN32 + if (errno != EAGAIN && errno != EWOULDBLOCK) +#else + if (WSAGetLastError() != WSAEWOULDBLOCK) +#endif + { + printf("IGMP: unable to send query for group %s:", + inet_ntoa(dst.sin_addr)); + log_socket_error("IGMP sendto()"); + break; + } + + cCondWait::SleepMs(10); + } +} + + +// ------------------------------------------------------------------------------------------------------------------------- + +cIgmpMain::cIgmpMain(cStreamer* streamer, iface_t bindif, int table) + : cThread("IGMP timeout handler") +{ + m_bindaddr = bindif.ipaddr; + m_bindif = bindif; + m_table = table; + + m_IgmpListener = new cIgmpListener(this); + m_StartupQueryCount = IGMP_STARTUP_QUERY_COUNT; + m_Querier = true; + m_streamer = streamer; + m_stopping = 0; + TV_CLR(m_GeneralQueryTimer); +} + +cIgmpMain::~cIgmpMain(void) +{ +} + +void cIgmpMain::Destruct(void) +{ + if (m_IgmpListener) + { + m_IgmpListener->Destruct(); + } + + // Wake up timeout thread in case it is currently sleeping + m_stopping=1; + m_CondWait.Signal(); + + // Now try to stop thread. After 3 seconds it will be canceled. + Cancel(3); + + cMulticastGroup *del = NULL; + + for (cMulticastGroup *group = m_Groups.First(); group; + group = m_Groups.Next(group)) + { + if ( group->stream ) + { + m_streamer->StopMulticast(group); + } + if (del) + m_Groups.Del(del); + del = group; + } + if (del) + m_Groups.Del(del); + +} + +bool cIgmpMain::StartListener(void) +{ + if ( m_IgmpListener && m_IgmpListener->Initialize(m_bindif, m_table) ) + { + Start(); + return true; + } + else + { + printf("cIgmpMain: Cannot create IGMP listener.\n"); + return false; + } +} + +void cIgmpMain::ProcessIgmpQueryMessage(in_addr_t groupaddr, + in_addr_t senderaddr) +{ + if (ntohl(senderaddr) < ntohl(m_bindaddr)) + { + in_addr sender; + sender.s_addr = senderaddr; + printf("IGMP: Another Querier with lower IP address (%s) " + "is active.\n", inet_ntoa(sender)); + IGMPStartOtherQuerierPresentTimer(); + } +} + +void cIgmpMain::ProcessIgmpReportMessage(int type, in_addr_t groupaddr, + in_addr_t senderaddr) +{ + cMulticastGroup* group; + + if ( !m_streamer->IsGroupinRange(groupaddr) ) + return; + + LOCK_THREAD; + + switch (type) + { + case IGMPV3_MR_MODE_IS_INCLUDE: + break; + + case IGMPV1_MEMBERSHIP_REPORT: + case IGMPV2_MEMBERSHIP_REPORT: + case IGMPV3_MR_MODE_IS_EXCLUDE: + case IGMPV3_MR_CHANGE_TO_EXCLUDE: + group = FindGroup(groupaddr); + if (!group) + { + group = new cMulticastGroup(groupaddr); + m_Groups.Add(group); + } + if (!group->stream) + { + m_streamer->StartMulticast(group); + } + IGMPStartTimer(group, senderaddr); + if (type == IGMPV1_MEMBERSHIP_REPORT) + IGMPStartV1HostTimer(group); + break; + + case IGMPV2_LEAVE_GROUP: + case IGMPV3_MR_CHANGE_TO_INCLUDE: + group = FindGroup(groupaddr); + if (group && !TV_SET(group->v1timer)) + { + if (group->reporter == senderaddr) + { + IGMPStartTimerAfterLeave(group, m_Querier ? + IGMP_LAST_MEMBER_QUERY_INTERVAL_TS : 1); + //Igmp->igmp_code); + if (m_Querier) + IGMPSendGroupQuery(group); + IGMPStartRetransmitTimer(group); + } + m_CondWait.Signal(); + } + break; + + case IGMPV3_MR_ALLOW_NEW_SOURCES: + printf("IGMPv3 Group Record Type \"ALLOW_NEW_SOURCES\" " + "ignored.\n"); + break; + case IGMPV3_MR_BLOCK_OLD_SOURCES: + printf("IGMPv3 Group Record Type \"BLOCK_OLD_SOURCES\" " + "ignored.\n"); + break; + default: + printf("Unknown IGMPv3 Group Record Type.\n"); + } +} + +void cIgmpMain::Action() +{ + while (Running()) + { + struct timeval now; + struct timeval next; + + gettimeofday(&now, NULL); + TV_CPY(next, now); + next.tv_sec += IGMP_QUERY_INTERVAL; + + cMulticastGroup *del = NULL; + + {// <-- ThreadLock is locked only within this block! + LOCK_THREAD; + + if (TV_CMP(m_GeneralQueryTimer, <, now)) { + if (!quiet) + printf("IGMP: Starting General Query.\n"); + IGMPSendGeneralQuery(); + IGMPStartGeneralQueryTimer(); + } + if (TV_CMP(next, >, m_GeneralQueryTimer)) + TV_CPY(next, m_GeneralQueryTimer); + + for (cMulticastGroup *group = m_Groups.First(); group; + group = m_Groups.Next(group)) + { + if (TV_CMP(group->timeout, <, now)) + { + m_streamer->StopMulticast(group); + IGMPClearRetransmitTimer(group); + if (del) + m_Groups.Del(del); + del = group; + } + else if (m_Querier && TV_SET(group->retransmit) + && TV_CMP(group->retransmit, <, now)) { + IGMPSendGroupQuery(group); + IGMPStartRetransmitTimer(group); + if (TV_CMP(next, >, + group->retransmit)) + TV_CPY(next, + group->retransmit); + } + else if (TV_SET(group->v1timer) && + TV_CMP(group->v1timer, <, now)) { + TV_CLR(group->v1timer); + } + else { + if (TV_CMP(next, >, group->timeout)) + TV_CPY(next, group->timeout); + if (TV_SET(group->retransmit) && + TV_CMP(next, >, + group->retransmit)) + TV_CPY(next, group->retransmit); + if (TV_SET(group->v1timer) && + TV_CMP(next, >, group->v1timer)) + TV_CPY(next, group->v1timer); + } + } + if (del) + m_Groups.Del(del); + } + + int sleep = (next.tv_sec - now.tv_sec) * 1000; + sleep += (next.tv_usec - now.tv_usec) / 1000; + if (next.tv_usec < now.tv_usec) + sleep += 1000; + if (m_stopping) + sleep = 100; + else if (!sleep) + continue; + if (!quiet) + printf("IGMP main thread: Sleeping %d ms\n", sleep); + m_CondWait.Wait(sleep); + } +} + +cMulticastGroup* cIgmpMain::FindGroup(in_addr_t Group) const +{ + cMulticastGroup *group = m_Groups.First(); + while (group && group->group != Group) + group = m_Groups.Next(group); + return group; +} + +void cIgmpMain::IGMPStartGeneralQueryTimer() +{ + m_Querier = true; + if (m_StartupQueryCount) { + gettimeofday(&m_GeneralQueryTimer, NULL); + m_GeneralQueryTimer.tv_sec += IGMP_STARTUP_QUERY_INTERVAL; + m_StartupQueryCount--; + } + else { + gettimeofday(&m_GeneralQueryTimer, NULL); + m_GeneralQueryTimer.tv_sec += IGMP_QUERY_INTERVAL; + } +} + +void cIgmpMain::IGMPStartOtherQuerierPresentTimer() +{ + m_Querier = false; + m_StartupQueryCount = 0; + gettimeofday(&m_GeneralQueryTimer, NULL); + m_GeneralQueryTimer.tv_sec += IGMP_OTHER_QUERIER_PRESENT_INTERVAL; +} + +void cIgmpMain::IGMPSendGeneralQuery() +{ + m_IgmpListener->IGMPSendQuery(IGMP_ALL_HOSTS, + IGMP_QUERY_RESPONSE_INTERVAL); +} + +void cIgmpMain::IGMPStartRetransmitTimer(cMulticastGroup* Group) +{ + gettimeofday(&Group->retransmit, NULL); + TV_ADD(Group->retransmit, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS); +} + +void cIgmpMain::IGMPClearRetransmitTimer(cMulticastGroup* Group) +{ + TV_CLR(Group->retransmit); +} + +// Group state actions +void cIgmpMain::IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member) +{ + gettimeofday(&Group->timeout, NULL); + Group->timeout.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL; + TV_CLR(Group->retransmit); + Group->reporter = Member; + +} + +void cIgmpMain::IGMPStartV1HostTimer(cMulticastGroup* Group) +{ + gettimeofday(&Group->v1timer, NULL); + Group->v1timer.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL; +} + +void cIgmpMain::IGMPStartTimerAfterLeave(cMulticastGroup* Group, + unsigned int MaxResponseTimeTs) +{ + //Group->Update(time(NULL) + MaxResponseTime + //* IGMP_LAST_MEMBER_QUERY_COUNT / 10); + MaxResponseTimeTs *= IGMP_LAST_MEMBER_QUERY_COUNT; + gettimeofday(&Group->timeout, NULL); + TV_ADD(Group->timeout, MaxResponseTimeTs); + TV_CLR(Group->retransmit); + Group->reporter = 0; +} + +void cIgmpMain::IGMPSendGroupQuery(cMulticastGroup* Group) +{ + m_IgmpListener->IGMPSendQuery(Group->group, + IGMP_LAST_MEMBER_QUERY_INTERVAL_TS); +} + diff --git a/mcast/netcv2dvbip/igmp.h b/mcast/netcv2dvbip/igmp.h new file mode 100644 index 0000000..9032e83 --- /dev/null +++ b/mcast/netcv2dvbip/igmp.h @@ -0,0 +1,123 @@ +#ifndef __IGMP_H +#define __IGMP_H + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +#include "clist.h" +#include "thread.h" +#include "misc.h" +#include "iface.h" + +#define IGMPV1_MEMBERSHIP_REPORT 0x12 /* Ver. 1 membership report */ +#define IGMPV2_MEMBERSHIP_REPORT 0x16 /* Ver. 2 membership report */ +#define IGMPV2_LEAVE_GROUP 0x17 /* Leave-group message */ + +#define IGMPV3_MR_MODE_IS_INCLUDE 0x01 +#define IGMPV3_MR_MODE_IS_EXCLUDE 0x02 +#define IGMPV3_MR_CHANGE_TO_INCLUDE 0x03 +#define IGMPV3_MR_CHANGE_TO_EXCLUDE 0x04 +#define IGMPV3_MR_ALLOW_NEW_SOURCES 0x05 +#define IGMPV3_MR_BLOCK_OLD_SOURCES 0x06 + +class cStream; +class cStreamer; + +class cMulticastGroup : public cListObject +{ + public: + cMulticastGroup(in_addr_t Group); + in_addr_t group; + in_addr_t reporter; + struct timeval timeout; + struct timeval v1timer; + struct timeval retransmit; + cStream* stream; +}; + +class cIgmpMain; + +class cIgmpListener : public cThread +{ + public: + cIgmpListener(cIgmpMain* igmpmain); + + bool Initialize(iface_t bindif, int table); + void Destruct(void); + bool Membership(in_addr_t mcaddr, bool Add); + void IGMPSendQuery(in_addr_t Group, int Timeout); + + private: + int m_socket; + in_addr_t m_bindaddr; + + cIgmpMain* m_IgmpMain; + + void Parse(char*, int); + + virtual void Action(); + +}; + +//------------------------------------------------------------------------------------------------------------------- + +class cIgmpMain : public cThread +{ + public: + cIgmpMain(cStreamer* streamer, iface_t bindif, int table); + ~cIgmpMain(void); + bool StartListener(void); + void Destruct(void); + void ProcessIgmpQueryMessage(in_addr_t Group, in_addr_t Sender); + void ProcessIgmpReportMessage(int type, in_addr_t Group, + in_addr_t Sender); + + private: + // Parent streamer + cStreamer* m_streamer; + + // Listener + cIgmpListener* m_IgmpListener; + in_addr_t m_bindaddr; + iface_t m_bindif; + int m_table; + + cList<cMulticastGroup> m_Groups; + + // General Query / Timer + struct timeval m_GeneralQueryTimer; + bool m_Querier; + int m_StartupQueryCount; + + void IGMPStartGeneralQueryTimer(); + void IGMPStartOtherQuerierPresentTimer(); + void IGMPSendGeneralQuery(); + + // Group Query / Timer + void IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member); + void IGMPStartV1HostTimer(cMulticastGroup* Group); + void IGMPStartTimerAfterLeave(cMulticastGroup* Group, + unsigned int MaxResponseTime); + void IGMPStartRetransmitTimer(cMulticastGroup* Group); + void IGMPClearRetransmitTimer(cMulticastGroup* Group); + void IGMPSendGroupQuery(cMulticastGroup* Group); + void IGMPStartMulticast(cMulticastGroup* Group); + void IGMPStopMulticast(cMulticastGroup* Group); + + // Main thread + virtual void Action(); + cCondWait m_CondWait; + int m_stopping; + + // Helper + cMulticastGroup* FindGroup(in_addr_t Group) const; + bool IsGroupinRange(in_addr_t groupaddr); +}; + +#endif diff --git a/mcast/netcv2dvbip/main.c b/mcast/netcv2dvbip/main.c new file mode 100644 index 0000000..3b31406 --- /dev/null +++ b/mcast/netcv2dvbip/main.c @@ -0,0 +1,400 @@ +#include "dvbipstream.h" +#include "iface.h" + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#ifndef WIN32 +#include <signal.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#ifndef APPLE +#define _LINUX_IN_H +#include <linux/mroute.h> +#endif +#else +#include <direct.h> +#ifdef __MINGW32__ +#include <getopt.h> +#endif +#endif + +#include "streamer.h" + +#define PORT_MAX 6 + +#ifndef WIN32 +// Local function Prototypes +static void signalHandler(int); + +// Global vars... +static int sighandled = 0; +#define GOT_SIGINT 0x01 +#define GOT_SIGHUP 0x02 +#define GOT_SIGTERM 0x04 +#endif + +channel_t *channels=NULL; +int channel_num=0; +int channel_max_num=0; +int channel_use_eit = 0; +int channel_use_sdt = 0; +int portnum = 12345; +int table = 0; +int quiet = 0; + +/*-------------------------------------------------------------------------*/ +channel_t * read_channel_list(char *filename, char *dirname) +{ + FILE *cf; + FILE *pf; + char buf[512]; + + cf=fopen(filename,"r"); + if (!cf) { + printf("Can't read %s: %s\n",filename,strerror(errno)); + return NULL; + } + + if (dirname) { +#ifdef WIN32 + if (((dirname[0]>='A'&&dirname[0]<='Z') || + (dirname[0]>='a'&&dirname[0]<='z')) && dirname[1]==':') { + if (_chdrive((dirname[0]&0xdf)-'A'+1)) { + printf("Can't access %s: %s\n", dirname, + strerror(errno)); + fclose(cf); + return NULL; + } + } +#endif + if (chdir(dirname)) { + printf("Can't access %s: %s\n", dirname, + strerror(errno)); + fclose(cf); + return NULL; + } + } + + pf =fopen("channels.m3u", "w"); + if (!pf) { + printf("Can't create %s: %s\n", "channels.m3u", + strerror(errno)); + fclose(cf); + return NULL; + } + fprintf(pf, "#EXTM3U\n"); + + while(fgets(buf,sizeof(buf),cf)) { + if (channel_num==channel_max_num) { + channel_max_num+=200; + channels=(channel_t*)realloc(channels, + channel_max_num*sizeof(channel_t)); + if (!channels) { + printf("out of memory\n"); + fclose(pf); + fclose(cf); + return NULL; + } + } + if (ParseLine(buf,&channels[channel_num])) { + int ip1 = (channel_num+1)/256; + int ip2 = (channel_num) - (ip1*256) + 1; + if (!quiet) + printf("%i: udp://@239.255.%i.%i:%i - %s \n", + channel_num+1, ip1, ip2, portnum, + channels[channel_num].name); + fprintf(pf, "#EXTINF: %i,%s\n", channel_num+1, + channels[channel_num].name); + fprintf(pf, "udp://@239.255.%i.%i:%i\n", ip1,ip2, + portnum); + if (channel_use_eit) + { + channels[channel_num].NumEitpids = 1; + channels[channel_num].eitpids[0] = 0x12; + } + if (channel_use_sdt) + { + channels[channel_num].NumSdtpids = 1; + channels[channel_num].sdtpids[0] = 0x11; + } + channel_num++; + } + } + printf("Read %i channels, M3U playlist file \"channels.m3u\" " + "generated.\n",channel_num); + fclose(pf); + fclose(cf); + return channels; +} +/*-------------------------------------------------------------------------*/ +int get_channel_num(void) +{ + return channel_num; +} +/*-------------------------------------------------------------------------*/ +int get_channel_name(int n, char *str, int maxlen) +{ +#ifndef WIN32 + snprintf(str,maxlen,"%04i_%s.ts",n+1,channels[n].name); +#else + sprintf(str,"%04i_%s.ts",n+1,channels[n].name); +#endif + while(*str) { + char c=*str; + if (c=='/' || c=='\\' || c==':' || c=='?' || c=='*' || + !isprint(c)) + *str='_'; + str++; + } + return 0; +} +/*-------------------------------------------------------------------------*/ +channel_t *get_channel_data(int n) +{ + return &channels[n]; +} + + +extern cmdline_t cmd; + +void usage (void) +{ + printf("Usage: netcv2dvbip " + "[-b <multicast interface>] " + "[-p <port>] " + "[-i <netceiver interface>] " + "[-c <channels.conf>] " + "[-e activate EIT PID (EPG)] " + "[-s activate SDT PID (may be required for EPG)] " +#ifdef MRT_TABLE + "[-t <routing table number> (requires " + "CONFIG_IP_MROUTE_MULTIPLE_TABLES)] " +#endif + "[-q be more quiet on the screen] " + "[-o <output directory>]\n"); + exit(1); +} + +/*-------------------------------------------------------------------------*/ +int main(int argc, char *argv[]) +{ + char c; + char *dirname = NULL; + char channels[_POSIX_PATH_MAX]; + strcpy(channels, "channels.conf"); + char bindiface[IFNAMSIZ]; + iface_t iflist[MAXIF]; + +#ifndef WIN32 + uid_t uid; + struct sigaction sa; + + uid = getuid(); + if(uid != 0) + { + printf("You must be root! Current uid: %d.\n",uid); + return(1); + } + + sa.sa_handler = signalHandler; + sa.sa_flags = 0; /* Interrupt system calls */ + sigemptyset(&sa.sa_mask); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + +#else + bool IsAdmin = false; + IsUserAdmin(&IsAdmin); + if( IsAdmin == false) + { + printf("You must have administrative privileges!\n"); + return(1); + } +#endif + + memset(bindiface, 0, sizeof(bindiface)); + + while(1) + { + int ret = getopt(argc,argv, "i:hc:b:p:esqo:" +#ifdef MRT_TABLE + "t:" +#endif + ); + if (ret==-1) + break; + + c=(char)ret; + + switch (c) { + case 'i': + strncpy(cmd.iface, optarg, IFNAMSIZ-1); + cmd.iface[IFNAMSIZ-1]=0; + break; + case 'c': + strncpy(channels, optarg, _POSIX_PATH_MAX-1); + channels[_POSIX_PATH_MAX-1]=0; + break; + case 'b': + strncpy(bindiface, optarg, IFNAMSIZ-1); + bindiface[IFNAMSIZ-1] = 0; + break; + case 'p': + portnum = atoi(optarg); + break; +#ifdef MRT_TABLE + case 't': + table = atoi(optarg); + break; +#endif + case 'h': + usage(); + return(0); + case 'e': + channel_use_eit = 1; + break; + case 's': + channel_use_sdt = 1; + break; + case 'q': + quiet = 1; + break; + case 'o': + dirname = optarg; + break; + } + } + + memset( iflist, 0, sizeof(iflist)); + int num_ifaces=discover_interfaces( (iface_t*) &iflist ); + if ( !num_ifaces ) + exit(-1); + + int ifindex = 0; + if ( strlen(bindiface) > 0 ) + { + int found = false; + while ( ifindex < num_ifaces ) + { + if (!strncmp(iflist[ifindex].name, bindiface, IFNAMSIZ)) + { + found = true; + break; + } + ifindex++; + } + if (!found) + { + printf("Cannot find interface %s. Exiting...\n", + bindiface); + exit(-1); + } + + } + + printf("Starting netcv2dvbip. Streams will be sent to port: %d\n", + portnum); + + if (!read_channel_list(channels, dirname)) + exit(-1); + + mcli_startup(); + +#ifdef WIN32 + if ( !IsVistaOrHigher() ) + { + printf("Windows version does not support MLDv2, enabling " + "internal MLDv2 reporter.\n"); + mld_client_init(cmd.iface); + } +#endif + + printf("\n"); + +#ifdef WIN32 + WSADATA ws; + WSAStartup(MAKEWORD (2, 2),&ws); +#endif + + cStreamer streamer; + + struct sockaddr_in listenaddr; + listenaddr.sin_addr.s_addr = iflist[ifindex].ipaddr; + printf("Listening on interface %s (%s)\n\n\n", iflist[ifindex].name, + inet_ntoa(listenaddr.sin_addr)); + + streamer.SetBindIf(iflist[ifindex]); + streamer.SetStreamPort(portnum); + streamer.SetTable(table); + streamer.SetNumGroups(get_channel_num()); + + streamer.Run(); + +#ifdef WIN32 + printf("\nPlease press enter to stop.\n\n\n"); + getchar(); + printf("Stopping...please wait...\n"); +#else + while (1) + { + // Process signaling... + if (sighandled) { + if (sighandled & GOT_SIGINT) { + sighandled &= ~GOT_SIGINT; + printf("\nGot SIGINT signal. Exiting.\n"); + break; + } + if (sighandled & GOT_SIGTERM) { + sighandled &= ~GOT_SIGTERM; + printf("Got SIGTERM signal. Exiting.\n"); + break; + } + } + usleep(10000); + } +#endif + + streamer.Stop(); +#ifdef WIN32 + if ( !IsVistaOrHigher() ) + { + mld_client_exit(); + } +#endif + printf("netcv2dvbip stopped.\n"); + +#ifdef WIN32 + WSACleanup(); +#endif + return 0; +} +/*-------------------------------------------------------------------------*/ +#ifndef WIN32 +/* + * Signal handler. Take note of the fact that the signal arrived + * so that the main loop can take care of it. + */ +static void signalHandler(int sig) { + switch (sig) { + case SIGINT: + sighandled |= GOT_SIGINT; + break; + case SIGTERM: + sighandled |= GOT_SIGTERM; + break; + /* XXX: Not in use. + case SIGHUP: + sighandled |= GOT_SIGHUP; + break; + */ + } +} +#endif diff --git a/mcast/netcv2dvbip/makefile b/mcast/netcv2dvbip/makefile new file mode 100644 index 0000000..75ff51c --- /dev/null +++ b/mcast/netcv2dvbip/makefile @@ -0,0 +1,61 @@ +CC=g++ +CFLAGS=-O3 +LDFLAGS=-s + +ARCH= $(shell $(CC) -dumpmachine) +APPLE_DARWIN = $(shell echo $(ARCH) | grep -q 'apple-darwin' && echo "1" || echo "0") +CYGWIN = $(shell echo $(ARCH) | grep -q 'cygwin' && echo "1" || echo "0") +MIPSEL = $(shell echo $(ARCH) | grep -q 'mipsel' && echo "1" || echo "0") + +ifeq ($(APPLE_DARWIN), 1) +DEFS:=$(DEFS) -I../common/darwin/include/ -DAPPLE +APPLE=1 +CFLAGS:= $(CFLAGS) -fno-common -Wall +else +CFLAGS:= $(CFLAGS) -Wall -Woverloaded-virtual +endif + +ifeq ($(MIPSEL),1) +DEFS:=$(DEFS) -DMIPSEL +XML_LIB:=-lxml2 +else +XML_INC:=`xml2-config --cflags` +XML_LIB:=`xml2-config --libs` +endif + +INCLUDES:=$(INCLUDES) -I../client -I../common $(XML_INC) +DEFS:=$(DEFS) -g -DCLIENT +LDADD:=$(LDADD) -L../client +STATICLIBS:=$(LIBS) ../client/libmcli.a $(XML_LIB) -lpthread +LIBS:=$(LIBS) $(XML_LIB) -lpthread -lmcli +LDFLAGS:=$(LDFLAGS) -Wl,--as-needed + +netcv2dvbip_OBJECTS=main.o parse.o mclilink.o siparser.o crc32.o clist.o stream.o thread.o misc.o streamer.o igmp.o iface.o + +all: netcv2dvbip netcv2dvbip-static + +MAKEDEP = $(CC) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): makefile + $(MAKEDEP) $(INCLUDES) $(netcv2dvbip_OBJECTS:%.o=%.c) > $@ + +-include $(DEPFILE) + +netcv2dvbip: $(netcv2dvbip_OBJECTS) ../client/libmcli.so + $(CC) $(LDFLAGS) $(netcv2dvbip_OBJECTS) $(LDADD) $(LIBS) -o $@ + +netcv2dvbip-static: $(netcv2dvbip_OBJECTS) ../client/libmcli.a + $(CC) $(LDFLAGS) $(netcv2dvbip_OBJECTS) $(LDADD) $(STATICLIBS) -o $@ + +../client/libmcli.so: ../client/libmcli.a + +../client/libmcli.a: + make -C ../client + +.c.o: + $(CC) $(DEFS) $(INCLUDES) $(CFLAGS) -c $< + + +clean: + $(RM) -f $(DEPFILE) *.o *~ netcv2dvbip netcv2dvbip-static + diff --git a/mcast/netcv2dvbip/mclilink.c b/mcast/netcv2dvbip/mclilink.c new file mode 100644 index 0000000..58d22ea --- /dev/null +++ b/mcast/netcv2dvbip/mclilink.c @@ -0,0 +1,659 @@ +#include "dvbipstream.h" + +#define BUFFER_SIZE (256*TS_PER_UDP*188) + +#ifdef WIN32 +#define CVT (char *) +#else +#define CVT +#endif + +int gen_pat(unsigned char *buf, unsigned int program_number, + unsigned int pmt_pid, unsigned int ts_cnt) +{ + int pointer_field=0; + int section_length=13; + int transport_stream_id=0; + int version_number=0; + int current_next_indicator=1; + int section_number=0; + int last_section_number=0; + int i=0; + u_long crc; + + buf[i++] = 0x47; + buf[i++] = 0x40; + buf[i++] = 0x00; + buf[i++] = 0x10 | (ts_cnt&0xf); + + buf[i++] = pointer_field; + buf[i++] = 0; // table_id + buf[i] = 0xB0; // section_syntax_indicator=1, 0, reserved=11 + buf[i++]|= (section_length>>8)&0x0f; + buf[i++] = section_length&0xff; + + buf[i++] = transport_stream_id>>8; + buf[i++] = transport_stream_id&0xff; + + buf[i++] = 0xc0 | ((version_number&0x1f) << 1) | + (current_next_indicator&1); + buf[i++] = section_number; + buf[i++] = last_section_number; + + buf[i++] = program_number>>8; + buf[i++] = program_number&0xff; + buf[i++] = 0xe0 | ((pmt_pid>>8)&0x1F); + buf[i++] = pmt_pid&0xff; + + crc=dvb_crc32 ((char *)buf+5, i-5); + buf[i++] = (crc>>24)&0xff; + buf[i++] = (crc>>16)&0xff; + buf[i++] = (crc>>8)&0xff; + buf[i++] = crc&0xff; + + for(;i<188;i++) { + buf[i] = 0xff; + } + +// printhex_buf ("PAT", buf, 188); + return i; +} + +/*-------------------------------------------------------------------------*/ +int mcli_handle_ts (unsigned char *buffer, size_t len, void *p) +{ + stream_info_t *si = (stream_info_t *) p; + + if(si->stop) + return len; + + int olen = len; + + int ret; + unsigned int i; + +again: + switch (si->si_state) { + case 3: + case 0: + si->psi.start = 0; + si->psi.len = 0; + si->si_state++; + goto again; + + case 1: + ret = 0; + for(i=0; i<len; i+=188) { + ret = ts2psi_data (buffer+i, &si->psi, 188, 0); + if(ret){ + break; + } + } + if (ret < 0) { + si->si_state = 0; + } + + if (ret == 1) { + if (!quiet) + printf ("Channel: %s - Got PAT\n", + si->cdata->name); + pmt_pid_list_t pat; + ret = parse_pat_sect (si->psi.buf, si->psi.len, &pat); + if (ret < 0) { + si->si_state = 0; + } else if (ret == 0) { +// print_pat (&pat.p, pat.pl, pat.pmt_pids); + unsigned int n; + for (n = 0; n < pat.pmt_pids; n++) { + if (pat.pl[n].program_number == + (unsigned int)si->cdata->sid) { + si->pmt_pid = + pat.pl[n].network_pmt_pid; + if (!quiet) + printf ("Channel: %s - SID " + "%d has PMT Pid %d\n", + si->cdata->name, + si->cdata->sid, + si->pmt_pid); + break; + } + } + if (pat.pmt_pids) { + free (pat.pl); + } + si->si_state++; + } + } + break; + case 4: + ret = 0; + for(i=0; i<len; i+=188) { + ret = ts2psi_data(buffer+i, &si->psi, 188, si->pmt_pid); + if(ret){ + break; + } + } + if (ret < 0) { + si->si_state = 2; + } + + if (ret == 1) { + if (!quiet) + printf ("Channel: %s - Got PMT\n", + si->cdata->name); + pmt_t hdr; + si_ca_pmt_t pm, es; + int es_pid_num; +// printhex_buf ("Section", si->psi.buf, si->psi.len); + si->fta=1; + ret = parse_pmt_ca_desc (si->psi.buf, si->psi.len, + si->cdata->sid, &pm, &es, &hdr, &si->fta, + NULL, &es_pid_num); + if (ret < 0) { + si->si_state = 2; + } else if (ret == 0) { + si->es_pidnum = get_pmt_es_pids (es.cad, + es.size, si->es_pids, 1); + if (si->es_pidnum <= 0) { + si->si_state = 2; + } else { + si->si_state++; + } + } + if (pm.size) { + free (pm.cad); + } + if (es.size) { + free (es.cad); + } + break; + + } + case 6: + /* + for(i=0; i<len; i+=188) { + ret = ts2psi_data (buffer+i, &si->psi, 188, 0x12); + if(ret==1){ + printf("Channel: %s - Got EIT\n", + si->cdata->name); + } + } + */ + pthread_mutex_lock(&si->lock_ts); + + switch(len) { + case 1*188: + case 2*188: + case 3*188: + case 4*188: + case 5*188: + case 6*188: + case 7*188: + break; + default: + err(CVT "Channel: %s - bad data length %d, skipping\n", + si->cdata->name,(int)len); + goto out; + } + + if(!si->wr) { + pthread_mutex_lock(&si->lock_bf); + if(si->free) { + si->wr=si->free; + si->free=si->free->next; + pthread_mutex_unlock(&si->lock_bf); + } else { + pthread_mutex_unlock(&si->lock_bf); + si->wr=(stream_buffer_t *) + malloc(sizeof(stream_buffer_t)); + if(!si->wr) { + err(CVT "Channel: %s - out of memory\n", + si->cdata->name); + goto out; + } + } + si->wr->next=NULL; + si->fill=0; + } + + i=TS_PER_UDP*188-si->fill; + + if(len>i) { + memcpy(si->wr->data+si->fill,buffer,i); + + pthread_mutex_lock(&si->lock_bf); + if(!si->head) + si->head=si->tail=si->wr; + else { + si->tail->next=si->wr; + si->tail=si->wr; + } + if(si->free) { + si->wr=si->free; + si->free=si->free->next; + pthread_mutex_unlock(&si->lock_bf); + } else { + pthread_mutex_unlock(&si->lock_bf); + si->wr=(stream_buffer_t *) + malloc(sizeof(stream_buffer_t)); + if(!si->wr) { + err(CVT "Channel: %s - out of memory\n", + si->cdata->name); + goto out; + } + } + si->wr->next=NULL; + si->fill=0; + buffer+=i; + len-=i; + } + + memcpy(si->wr->data+si->fill,buffer,len); + si->fill+=len; + + if(si->fill==TS_PER_UDP*188) { + pthread_mutex_lock(&si->lock_bf); + if(!si->head) + si->head=si->tail=si->wr; + else { + si->tail->next=si->wr; + si->tail=si->wr; + } + pthread_mutex_unlock(&si->lock_bf); + si->wr=NULL; + } + +out: pthread_mutex_unlock(&si->lock_ts); + break; + } + + return olen; +} + +/*-------------------------------------------------------------------------*/ +int mcli_handle_ten (tra_t * ten, void *p) +{ + if(ten) { + + stream_info_t *si = (stream_info_t *) p; + printf("Channel: %s - Status: %02X, Strength: %04X, SNR: %04X," + " BER: %04X\n", si->cdata->name, ten->s.st, + ten->s.strength, ten->s.snr, ten->s.ber); + } + return 0; +} + +#ifdef WIN32THREADS +DWORD WINAPI stream_watch(__in LPVOID p) +#else +void *stream_watch (void *p) +#endif +{ + unsigned char ts[188]; + stream_info_t *si = (stream_info_t *) p; + if (!quiet) + printf("Channel: %s - stream watch thread started.\n", + si->cdata->name); + while (1 ) { + if(si->stop) + break; + if (si->pmt_pid && si->si_state == 2) { + dvb_pid_t pids[3]; + memset (&pids, 0, sizeof (pids)); + pids[0].pid = si->pmt_pid; + pids[1].pid = -1; + if (!quiet) + printf ("Channel: %s - Add PMT-PID: %d\n", + si->cdata->name, si->pmt_pid); + recv_pids (si->r, pids); + si->si_state++; + } + if (si->es_pidnum && si->si_state == 5) { + int i,k=0; + size_t sz = sizeof(dvb_pid_t) * (si->es_pidnum+2 + + si->cdata->NumEitpids + si->cdata->NumSdtpids); + dvb_pid_t *pids=(dvb_pid_t*)malloc(sz); + if(pids==NULL) { + err(CVT "Channel: %s - Can't get memory for " + "pids\n", si->cdata->name); + goto out; + } + memset (pids, 0, sz); + pids[k++].pid = si->pmt_pid; + //EIT PIDs + for (i = 0; i < si->cdata->NumEitpids; i++) + { + pids[k++].pid = si->cdata->eitpids[i]; + if (!quiet) + printf("Channel: %s - Add EIT-PID: " + "%d\n", si->cdata->name, + si->cdata->eitpids[i]); + } + //SDT PIDs + for (i = 0; i < si->cdata->NumSdtpids; i++) + { + pids[k++].pid = si->cdata->sdtpids[i]; + if (!quiet) + printf("Channel: %s - Add SDT-PID: " + "%d\n", si->cdata->name, + si->cdata->sdtpids[i]); + } + for (i = 0; i < si->es_pidnum; i++) { + if (!quiet) + printf ("Channel: %s - Add ES-PID: " + "%d\n", si->cdata->name, + si->es_pids[i]); + pids[i + k].pid = si->es_pids[i]; +// if(si->cdata->NumCaids) { + if(!si->fta) { + if (!quiet) + printf("Channel: %s - %s\n", + si->cdata->name, + si->fta ? "Free-To-Air": + "Crypted"); + pids[i + k].id = si->cdata->sid; + } + pids[i + k +1].pid = -1; + } + recv_pids (si->r, pids); + free(pids); + si->si_state++; + } + if(si->si_state == 6) { + gen_pat(ts, si->cdata->sid, si->pmt_pid, si->ts_cnt++); + mcli_handle_ts (ts, 188, si); + } +out: usleep (50000); + } + if (!quiet) + printf("Channel: %s - stream watch thread stopped.\n", + si->cdata->name); + return NULL; +} + +/*-------------------------------------------------------------------------*/ +void *mcli_stream_setup (const int channum) +{ + int cnum; + stream_info_t *si; + recv_info_t *r; + struct dvb_frontend_parameters fep; + recv_sec_t sec; + dvb_pid_t pids[4]; + int source = -1; + fe_type_t tuner_type = FE_ATSC; + + cnum = channum-1; +// printf ("mcli_stream_setup %i\n", cnum); + if (cnum < 0 || cnum > get_channel_num ()) + return 0; + + si = (stream_info_t *) malloc (sizeof (stream_info_t)); + if (!si) { + fprintf (stderr, "Cannot get memory for receiver\n"); + return NULL; + } + memset(si, 0, sizeof(stream_info_t)); + + si->psi.buf = (unsigned char *) malloc (PSI_BUF_SIZE); + if (!si->psi.buf) { + fprintf (stderr, "Cannot get memory for receiver\n"); + free (si); + return NULL; + } + + r = recv_add (); + if (!r) { + fprintf (stderr, "Cannot get memory for receiver\n"); + free (si->psi.buf); + free (si); + return NULL; + } + si->r = r; + + pthread_mutex_init (&si->lock_bf, NULL); + pthread_mutex_init (&si->lock_ts, NULL); + + if (!quiet) + register_ten_handler (r, mcli_handle_ten, si); + register_ts_handler (r, mcli_handle_ts, si); + + si->cdata = get_channel_data (cnum); + + memset (&fep, 0, sizeof (struct dvb_frontend_parameters)); + memset (&sec, 0, sizeof (recv_sec_t)); + + fep.frequency = si->cdata->frequency; + fep.inversion = INVERSION_AUTO; + // DVB-S + if (si->cdata->source >= 0) { + fep.u.qpsk.symbol_rate = si->cdata->srate * 1000; + fep.u.qpsk.fec_inner = (fe_code_rate_t)(si->cdata->coderateH | + (si->cdata->modulation<<16)); + fep.frequency *= 1000; + fep.inversion = (fe_spectral_inversion_t)si->cdata->inversion; + + sec.voltage = (fe_sec_voltage_t)si->cdata->polarization; + sec.mini_cmd = (fe_sec_mini_cmd_t)0; + sec.tone_mode = (fe_sec_tone_mode_t)0; + tuner_type = FE_QPSK; + source = si->cdata->source; + } + // DVB-T + else if (si->cdata->source == -2) { + fep.u.ofdm.constellation = + (fe_modulation_t)si->cdata->modulation; + fep.u.ofdm.code_rate_HP = (fe_code_rate_t)si->cdata->coderateH; + fep.u.ofdm.code_rate_LP = (fe_code_rate_t)si->cdata->coderateL; + fep.inversion = (fe_spectral_inversion_t)si->cdata->inversion; + fep.u.ofdm.bandwidth = (fe_bandwidth_t)si->cdata->bandwidth; + fep.u.ofdm.guard_interval = + (fe_guard_interval_t)si->cdata->guard; + fep.u.ofdm.transmission_mode = + (fe_transmit_mode_t)si->cdata->transmission; + fep.u.ofdm.hierarchy_information = + (fe_hierarchy_t)si->cdata->hierarchy; + + tuner_type = FE_OFDM; + source = si->cdata->source; + } + // DVB-C + else if (si->cdata->source == -3) { + fep.u.qam.symbol_rate = si->cdata->srate * 1000; + fep.u.qam.fec_inner = (fe_code_rate_t)si->cdata->coderateH; + fep.u.qam.modulation = (fe_modulation_t)si->cdata->modulation; + fep.frequency *= 1000; + fep.inversion = (fe_spectral_inversion_t)si->cdata->inversion; + + tuner_type = FE_QAM; + source = si->cdata->source; + } + + + + memset (&pids, 0, sizeof (pids)); + + pids[0].pid = 0; // PAT + pids[1].pid = -1; + + if (!quiet) + printf ("Tuning: source: %s, frequency: %i, PAT pid %i, " + "symbol rate %i\n", source>=0 ? "DVB-S(2)" : + source==-2 ? "DVB-T" : source==-3 ? "DVB-C" : "unknown", + si->cdata->frequency, pids[0].pid, + fep.u.qpsk.symbol_rate); + + recv_tune (r, tuner_type, source, &sec, &fep, pids); + +#ifdef WIN32THREADS + CreateThread(NULL, 0, stream_watch, si, 0, NULL); +#else + pthread_create (&si->t, NULL, stream_watch, si); +#endif +// printf("mcli_setup %p\n",si); + return si; +} + +/*-------------------------------------------------------------------------*/ +size_t mcli_stream_access (void *handle, char **buf) +{ + stream_info_t *si = (stream_info_t *) handle; + + if (!handle) + return 0; + if (si->stop) + return 0; + + pthread_mutex_lock(&si->lock_bf); + if(!si->head) { + pthread_mutex_unlock(&si->lock_bf); + return 0; + } + si->rd=si->head; + si->head=si->head->next; + pthread_mutex_unlock(&si->lock_bf); + + *buf=si->rd->data; + return TS_PER_UDP*188; +} + +/*-------------------------------------------------------------------------*/ +size_t mcli_stream_part_access (void *handle, char **buf) +{ + stream_info_t *si = (stream_info_t *) handle; + + if (!handle) + return 0; + if (si->stop) + return 0; + + pthread_mutex_lock(&si->lock_ts); + if(!si->head) { + int len; + + if(!si->wr) + len=0; + else { + si->rd=si->wr; + si->wr=NULL; + len=si->fill; + *buf=si->rd->data; + } + pthread_mutex_unlock(&si->lock_ts); + return len; + } + si->rd=si->head; + si->head=si->head->next; + pthread_mutex_unlock(&si->lock_ts); + + *buf=si->rd->data; + return TS_PER_UDP*188; +} + +/*-------------------------------------------------------------------------*/ +void mcli_stream_skip (void *handle) +{ + stream_info_t *si = (stream_info_t *) handle; + + if (!handle) + return; + + pthread_mutex_lock(&si->lock_bf); + si->rd->next=si->free; + si->free=si->rd; + pthread_mutex_unlock(&si->lock_bf); + + si->rd=NULL; +} + +/*-------------------------------------------------------------------------*/ +int mcli_stream_stop (void *handle) +{ + if (handle) { + stream_info_t *si = (stream_info_t *) handle; + recv_info_t *r = si->r; + if (pthread_exist(si->t)) { + si->stop = 1; + pthread_join (si->t, NULL); + } + + if (r) { + pthread_mutex_lock(&si->lock_ts); + register_ten_handler (r, NULL, NULL); + register_ts_handler (r, NULL, NULL); + pthread_mutex_unlock(&si->lock_ts); + recv_stop(r); + sleep(2); + recv_del (r); + } + if (si->psi.buf) { + free (si->psi.buf); + } + + if (si->rd) + free (si->rd); + if (si->wr) + free (si->wr); + while (si->head) { + stream_buffer_t *e; + e = si->head; + si->head = si->head->next; + free (e); + } + while (si->free) { + stream_buffer_t *e; + e = si->free; + si->free = si->free->next; + free (e); + } + + pthread_mutex_destroy(&si->lock_ts); + pthread_mutex_destroy(&si->lock_bf); + free (si); + } + return 0; +} + +cmdline_t cmd = { 0 }; + +/*-------------------------------------------------------------------------*/ +void mcli_startup (void) +{ +#ifdef PTW32_STATIC_LIB + pthread_win32_process_attach_np(); +#endif + netceiver_info_list_t *nc_list = nc_get_list (); + +#if defined WIN32 || defined APPLE + cmd.mld_start = 1; +#endif + +// printf ("Using Interface %s\n", cmd.iface); + recv_init (cmd.iface, cmd.port); + + if (cmd.mld_start) { + mld_client_init (cmd.iface); + } +#if 1 + int n, i; + printf ("Looking for netceivers out there....\n"); + while (1) { + nc_lock_list (); + for (n = 0; n < nc_list->nci_num; n++) { + netceiver_info_t *nci = nc_list->nci + n; + printf ("\nFound NetCeiver: %s\n", nci->uuid); + if (!quiet) + for (i = 0; i < nci->tuner_num; i++) { + printf (" Tuner: %s, Type %d\n", + nci->tuner[i].fe_info.name, + nci->tuner[i].fe_info.type); + } + } + nc_unlock_list (); + if (nc_list->nci_num) { + break; + } + sleep (1); + } +#endif +} diff --git a/mcast/netcv2dvbip/mingw/Makefile b/mcast/netcv2dvbip/mingw/Makefile new file mode 100644 index 0000000..aa6ac0d --- /dev/null +++ b/mcast/netcv2dvbip/mingw/Makefile @@ -0,0 +1,19 @@ +OBJS=main.o parse.o mclilink.o siparser.o crc32.o clist.o stream.o thread.o misc.o streamer.o igmp.o iface.o +EXE=netcv2dvbip.exe +CC:=gcc +CXX=g++ +CFLAGS:= -O2 -DCLIENT -DSTATICLIB -Wall -I../../common/win32/include -I../../common -I../../client +LDFLAGS:= -s -L../../common/win32/lib +LDLIBS:= -lmingwex -lmcli -liphlpapi -lpthreadGC2 -lws2_32 -lxml2 -lz + +all: $(EXE) + +$(EXE): $(OBJS) + $(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS) + +%.o: ../%.c + $(CXX) -c $(CFLAGS) -o $@ $< + +clean: + @-del $(EXE) *.o *.la *~ + diff --git a/mcast/netcv2dvbip/mingw/build.cmd b/mcast/netcv2dvbip/mingw/build.cmd new file mode 100644 index 0000000..3d3efdb --- /dev/null +++ b/mcast/netcv2dvbip/mingw/build.cmd @@ -0,0 +1,2 @@ +@set PATH=c:\MinGw\bin;%PATH% +@mingw32-make diff --git a/mcast/netcv2dvbip/mingw/clean.cmd b/mcast/netcv2dvbip/mingw/clean.cmd new file mode 100644 index 0000000..e43e4db --- /dev/null +++ b/mcast/netcv2dvbip/mingw/clean.cmd @@ -0,0 +1,2 @@ +@set PATH=c:\MinGw\bin;%PATH% +@mingw32-make clean diff --git a/mcast/netcv2dvbip/misc.c b/mcast/netcv2dvbip/misc.c new file mode 100644 index 0000000..aff2958 --- /dev/null +++ b/mcast/netcv2dvbip/misc.c @@ -0,0 +1,193 @@ +#include <stdio.h> + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include <winsock2.h> +#else +#include <sys/time.h> +#endif + +#include <pthread.h> +#include "misc.h" + +void log_socket_error(const char* msg) +{ +#ifdef WIN32 + printf(msg); + printf(": WSAGetLastError(): %d", WSAGetLastError()); +#else + perror(msg); +#endif +} + +#ifdef WIN32 +static const unsigned __int64 epoch = 116444736000000000; + +int gettimeofday(struct timeval * tp, struct timezone * tzp) +{ + FILETIME file_time; + SYSTEMTIME system_time; + ULARGE_INTEGER ularge; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + ularge.LowPart = file_time.dwLowDateTime; + ularge.HighPart = file_time.dwHighDateTime; + + tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L); + tp->tv_usec = (long) (system_time.wMilliseconds * 1000); + + return 0; +} +#endif + +bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow) +{ + struct timeval now; + + if (!gettimeofday(&now, NULL)) { + now.tv_sec += MillisecondsFromNow / 1000; + now.tv_usec += (MillisecondsFromNow % 1000) * 1000; + if (now.tv_usec >= 1000000) { + now.tv_sec++; + now.tv_usec -= 1000000; + } + Abstime->tv_sec = now.tv_sec; + Abstime->tv_nsec = now.tv_usec * 1000; + return true; + } + return false; +} + +// --- cTimeMs --------------------------------------------------------------- + +cTimeMs::cTimeMs(int Ms) +{ + Set(Ms); +} + +uint64_t cTimeMs::Now(void) +{ + struct timeval t; + + if (gettimeofday(&t, NULL) == 0) + return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000; + return 0; +} + +void cTimeMs::Set(int Ms) +{ + begin = Now() + Ms; +} + +bool cTimeMs::TimedOut(void) +{ + return Now() >= begin; +} + +uint64_t cTimeMs::Elapsed(void) +{ + return Now() - begin; +} + +#ifdef WIN32 + +#ifdef __MINGW32__ +#define __try if(1) +#define __leave goto out +#define __finally if(1) +#endif + +bool IsUserAdmin( bool* pbAdmin ) +{ +#if WINVER < 0x0500 + HANDLE hAccessToken = NULL; + PBYTE pInfoBuffer = NULL; + DWORD dwInfoBufferSize = 1024; // starting buffer size + PTOKEN_GROUPS ptgGroups = NULL; +#endif + PSID psidAdministrators = NULL; + SID_IDENTIFIER_AUTHORITY siaNtAuthority = {SECURITY_NT_AUTHORITY}; + BOOL bResult = FALSE; + +__try +{ + + // initialization of the security id + if( !AllocateAndInitializeSid( + &siaNtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, 0,0,0,0,0,0, &psidAdministrators ) ) + __leave; + +#if WINVER < 0x0500 + // code for the OS < W2k + // open process token + if( !OpenProcessToken( GetCurrentProcess(),TOKEN_QUERY,&hAccessToken ) ) + __leave; + + do // lets make a buffer for the information from the token + { + if( pInfoBuffer ) + delete pInfoBuffer; + pInfoBuffer = new BYTE[dwInfoBufferSize]; + if( !pInfoBuffer ) + __leave; + SetLastError( 0 ); + if( !GetTokenInformation( hAccessToken, TokenGroups, + pInfoBuffer, dwInfoBufferSize, &dwInfoBufferSize ) && + ( ERROR_INSUFFICIENT_BUFFER != GetLastError() ) ) + __leave; + else + ptgGroups = (PTOKEN_GROUPS)pInfoBuffer; + } + while( GetLastError() ); // if we encounter an error, then the + // initializing buffer too small, + // lets make it bigger + + // lets check the security id one by one, while we find one we need + for( UINT i = 0; i < ptgGroups->GroupCount; i++ ) + { + if( EqualSid(psidAdministrators,ptgGroups->Groups[i].Sid) ) + { + *pbAdmin = TRUE; + bResult = TRUE; + __leave; + } + } +#else + // this is code for the W2K + bResult = CheckTokenMembership( NULL, psidAdministrators, pbAdmin ); +#endif +} + +__finally +{ +#ifdef __MINGW32__ +out: +#endif +#if WINVER < 0x0500 + if( hAccessToken ) + CloseHandle( hAccessToken ); + if( pInfoBuffer ) + delete pInfoBuffer; +#endif + if( psidAdministrators ) + FreeSid( psidAdministrators ); +} + +return bResult; +} + +bool IsVistaOrHigher() +{ + OSVERSIONINFO osvi; + + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&osvi); + + return (osvi.dwMajorVersion >= 6); +} + +#endif diff --git a/mcast/netcv2dvbip/misc.h b/mcast/netcv2dvbip/misc.h new file mode 100644 index 0000000..edc269d --- /dev/null +++ b/mcast/netcv2dvbip/misc.h @@ -0,0 +1,67 @@ +#ifndef __MISC_H +#define __MISC_H + +#ifdef _MSC_VER + typedef unsigned __int64 uint64_t; // Define it from MSVC's internal type +#else + #include <stdint.h> // Use the C99 official header +#endif + +#define bool int + +#ifndef __STL_CONFIG_H // in case some plugin needs to use the STL +template<class T> inline T xmin(T a, T b) { return a <= b ? a : b; } +template<class T> inline T xmax(T a, T b) { return a >= b ? a : b; } +template<class T> inline int xsgn(T a) { return a < 0 ? -1 : a > 0 ? 1 : 0; } +template<class T> inline void xswap(T &a, T &b) { T t = a; a = b; b = t; } +#endif + +#ifdef WIN32 +typedef unsigned long in_addr_t; +#define SCKT SOCKET +#else +#include <arpa/inet.h> +#define SCKT int +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#ifdef WIN32 +#define strdup _strdup +#endif + +#define HI_BYTE(byte) (((byte) >> 4) & 0x0F) +#define LO_BYTE(byte) ((byte) & 0x0F) + +extern void log_socket_error(const char* msg); +extern bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow); + +#ifdef WIN32 +struct timezone { + int tz_minuteswest; /* minutes west of Greenwich */ + int tz_dsttime; /* type of DST correction */ +}; +extern int gettimeofday(struct timeval * tp, struct timezone * tzp); +bool IsUserAdmin( bool* pbAdmin ); +bool IsVistaOrHigher(); +#else +#include <sys/time.h> +#endif + +class cTimeMs { +private: + uint64_t begin; +public: + cTimeMs(int Ms = 0); + ///< Creates a timer with ms resolution and an initial timeout of Ms. + static uint64_t Now(void); + void Set(int Ms = 0); + bool TimedOut(void); + uint64_t Elapsed(void); +}; + +extern int quiet; + +#endif diff --git a/mcast/netcv2dvbip/parse.c b/mcast/netcv2dvbip/parse.c new file mode 100644 index 0000000..ab60e9e --- /dev/null +++ b/mcast/netcv2dvbip/parse.c @@ -0,0 +1,496 @@ +#include "dvbipstream.h" + +typedef struct +{ + int userValue; + int driverValue; +} tChannelParameterMap; + +const tChannelParameterMap RolloffValues[] = { + {0, 0}, + {35, 0}, + {25, 0}, + {20, 0}, + {-1} +}; + +const tChannelParameterMap InversionValues[] = { + {0, INVERSION_OFF}, + {1, INVERSION_ON}, + {999, INVERSION_AUTO}, + {-1} +}; + +const tChannelParameterMap BandwidthValues[] = { + {6, BANDWIDTH_6_MHZ}, + {7, BANDWIDTH_7_MHZ}, + {8, BANDWIDTH_8_MHZ}, + {999, BANDWIDTH_AUTO}, + {-1} +}; + +const tChannelParameterMap CoderateValues[] = { + {0, FEC_NONE}, + {12, FEC_1_2}, + {23, FEC_2_3}, + {34, FEC_3_4}, + {45, FEC_4_5}, + {56, FEC_5_6}, + {67, FEC_6_7}, + {78, FEC_7_8}, + {89, FEC_8_9}, + {999, FEC_AUTO}, + {-1} +}; + +const tChannelParameterMap CoderateValuesS[] = { + {0, FEC_NONE}, + {12, FEC_1_2}, + {23, FEC_2_3}, + {34, FEC_3_4}, + {45, FEC_4_5}, + {56, FEC_5_6}, + {67, FEC_6_7}, + {78, FEC_7_8}, + {89, FEC_8_9}, + {13, FEC_1_3}, //S2 + {14, FEC_1_4}, //S2 + {25, FEC_2_5}, //S2 + {35, FEC_3_5}, //S2 + {910, FEC_9_10}, //S2 + {999, FEC_AUTO}, + {-1} +}; + + +const tChannelParameterMap ModulationValues[] = { + {0, QPSK}, + {16, QAM_16}, + {32, QAM_32}, + {64, QAM_64}, + {128, QAM_128}, + {256, QAM_256}, + {999, QAM_AUTO}, + {-1} +}; + +const tChannelParameterMap ModulationValuesS[] = { + {2, QPSK}, + {4, QPSK}, + {5, PSK8}, + {42, QPSK_S2}, // S2 + {8, PSK8}, + {16, QAM_16}, + {32, QAM_32}, + {64, QAM_64}, + {128, QAM_128}, + {256, QAM_256}, + {999, QAM_AUTO}, + {-1} +}; + +const tChannelParameterMap TransmissionValues[] = { + {2, TRANSMISSION_MODE_2K}, + {8, TRANSMISSION_MODE_8K}, + {999, TRANSMISSION_MODE_AUTO}, + {-1} +}; + +const tChannelParameterMap GuardValues[] = { + {4, GUARD_INTERVAL_1_4}, + {8, GUARD_INTERVAL_1_8}, + {16, GUARD_INTERVAL_1_16}, + {32, GUARD_INTERVAL_1_32}, + {999, GUARD_INTERVAL_AUTO}, + {-1} +}; + +const tChannelParameterMap HierarchyValues[] = { + {0, HIERARCHY_NONE}, + {1, HIERARCHY_1}, + {2, HIERARCHY_2}, + {4, HIERARCHY_4}, + {999, HIERARCHY_AUTO}, + {-1} +}; + +#ifdef WIN32 +#define strtok_r mystrtok +char *strtok_r(char *s, const char *d, char **m) +{ + char *p; + char *q; + + if (s) + *m = s; + + p = *m; + if (!p) + return NULL; + + while (*p && strchr(d,*p)) + p++; + + if (*p == 0) { + *m = NULL; + return NULL; + } + + q = p; + while (*q && !strchr(d,*q)) + q++; + + if (!*q) + *m = NULL; + else { + *q++ = 0; + *m = q; + } + + return p; +} +#endif + +char *strreplace (char *s, char c1, char c2) +{ + if (s) { + char *p = s; + while (*p) { + if (*p == c1) + *p = c2; + p++; + } + } + return s; +} + + +int UserIndex (int Value, const tChannelParameterMap * Map) +{ + const tChannelParameterMap *map = Map; + while (map && map->userValue != -1) { + if (map->userValue == Value) + return map - Map; + map++; + } + return -1; +} + +int DriverIndex (int Value, const tChannelParameterMap * Map) +{ + const tChannelParameterMap *map = Map; + while (map && map->userValue != -1) { + if (map->driverValue == Value) + return map - Map; + map++; + } + return -1; +} + +int MapToDriver (int Value, const tChannelParameterMap * Map) +{ + int n = UserIndex (Value, Map); + if (n >= 0) + return Map[n].driverValue; + return -1; +} + +static const char *ParseParameter (const char *s, int *Value, + const tChannelParameterMap * Map) +{ + if (*++s) { + char *p = NULL; + int n; + errno = 0; + n = strtol (s, &p, 10); + if (!errno && p != s) { + *Value = MapToDriver (n, Map); + if (*Value >= 0) + return p; + } + } + printf ("ERROR: invalid value for parameter '%c'\n", *(s - 1)); + return NULL; +} + +bool StringToParameters (const char *s, channel_t * ch) +{ + int dummy; + bool newformat = false; + int deliverysystem = -1; + const char * start = s; + + while (s && *s) { + switch (toupper (*s)) { + case 'B': + s = ParseParameter (s, &ch->bandwidth, BandwidthValues); + break; + case 'C': + s = ParseParameter (s, &ch->coderateH, CoderateValuesS); + break; + case 'D': + s = ParseParameter (s, &ch->coderateL, CoderateValues); + break; + case 'O': + newformat = true; + case 'E': + s = ParseParameter (s, &dummy, RolloffValues); + break; + case 'G': + s = ParseParameter (s, &ch->guard, GuardValues); + break; + case 'H': + ch->polarization = SEC_VOLTAGE_18; + s++; + break; + case 'I': + s = ParseParameter (s, &ch->inversion, InversionValues); + break; + case 'L': + ch->polarization = SEC_VOLTAGE_18; + s++; + break; + case 'M': + s = ParseParameter (s, &ch->modulation, + ModulationValuesS); + break; + case 'R': + ch->polarization = SEC_VOLTAGE_13; + s++; + break; + case 'S': + newformat = true; + s++; + if (*s == '1') + deliverysystem = 1; + else if (*s == '0') + deliverysystem = 0; + s++; + break; + case 'T': + s = ParseParameter (s, &ch->transmission, + TransmissionValues); + break; + case 'V': + ch->polarization = SEC_VOLTAGE_13; + s++; + break; + case 'Y': + s = ParseParameter (s, &ch->hierarchy, HierarchyValues); + break; + default: + printf ("ERROR: unknown parameter key '%c' at pos %d\n", + *s, (int)((long)(s-start))); + return 0; + } + } + if (newformat) + { + //printf("Detected VDR-1.7.x parameter string format.\n"); + if (deliverysystem == 1 && ch->modulation == QPSK) + { + ch->modulation = QPSK_S2; + } + } + return 1; +} + +int SourceFromString (char *s) +{ + char t = 0, d = 0; + float val = 0; + + sscanf (s, "%c%f%c", &t, &val, &d); + if (t == 'S') { + val = val * 10; + if (d == 'W') + val = 1800 - val; + else + val = val + 1800; + } else if (t == 'T') + val = -2; + else if (t == 'C') + val = -3; + return (int) val; +} + +int ParseLine (const char *s, channel_t * ch) +{ + bool ok = 1; + memset (ch, 0, sizeof (channel_t)); + + if (*s == ':') { +#if 0 + groupSep = true; + if (*++s == '@' && *++s) { + char *p = NULL; + errno = 0; + int n = strtol (s, &p, 10); + if (!errno && p != s && n > 0) { + ch->number = n; + s = p; + } + } + ch->name = strdup (skipspace (s)); + strreplace (ch->name, '|', ':'); +#endif + ok = 0; + } else { +// groupSep = false; + char *namebuf = NULL; + char *sourcebuf = NULL; + char *parambuf = NULL; + char *vpidbuf = NULL; + char *apidbuf = NULL; + char *caidbuf = NULL; + int fields; +#if ! (defined WIN32 || defined APPLE) + fields = sscanf (s, "%a[^:]:%d :%a[^:]:%a[^:] :%d :%a[^:]:" + "%a[^:]:%d :%a[^:]:%d :%d :%d :%d ", &namebuf, + &ch->frequency, ¶mbuf, &sourcebuf, &ch->srate, + &vpidbuf, &apidbuf, &ch->tpid, &caidbuf, &ch->sid, + &ch->nid, &ch->tid, &ch->rid); +#else + namebuf = (char *) malloc (1024); + parambuf = (char *) malloc (1024); + sourcebuf = (char *) malloc (1024); + vpidbuf = (char *) malloc (1024); + apidbuf = (char *) malloc (1024); + caidbuf = (char *) malloc (1024); + fields = sscanf (s, "%[^:]:%d :%[^:]:%[^:] :%d :%[^:]:%[^:]:" + "%d :%[^:]:%d :%d :%d :%d ", namebuf, &ch->frequency, + parambuf, sourcebuf, &ch->srate, vpidbuf, apidbuf, + &ch->tpid, caidbuf, &ch->sid, &ch->nid, &ch->tid, + &ch->rid); + +#endif + if (fields >= 9) { + if (fields == 9) { + // allow reading of old format + ch->sid = atoi (caidbuf); + free (caidbuf); + caidbuf = NULL; + ch->caids[0] = ch->tpid; + ch->caids[1] = 0; + ch->tpid = 0; + } + ch->vpid = ch->ppid = 0; + ch->apids[0] = 0; + ch->dpids[0] = 0; + ok = false; + if (parambuf && sourcebuf && vpidbuf && apidbuf) { + char *p, *dpidbuf, *q, *strtok_next; +// int NumApids; + ok = StringToParameters (parambuf, ch); + ch->source = SourceFromString (sourcebuf); + ok = ok && ((ch->source >= 0) || + (ch->source == -2)||(ch->source == -3)); + p = strchr (vpidbuf, '+'); + if (p) + *p++ = 0; + if (sscanf (vpidbuf, "%d", &ch->vpid) != 1) + return false; + if (p) { + if (sscanf (p, "%d", &ch->ppid) != 1) + return false; + } else + ch->ppid = ch->vpid; + + dpidbuf = strchr (apidbuf, ';'); + if (dpidbuf) + *dpidbuf++ = 0; + p = apidbuf; + + ch->NumApids = 0; + + while ((q = strtok_r (p, ",", &strtok_next))) { + if (ch->NumApids < MAXAPIDS) { + ch->apids[ch->NumApids++] = + strtol (q, NULL, 10); + } else + // no need to set ok to 'false' + printf ("ERROR: too many APIDs!" + "\n"); + p = NULL; + } + ch->apids[ch->NumApids] = 0; + if (dpidbuf) { + char *p = dpidbuf; + char *q; + int NumDpids = 0; + char *strtok_next; + while ((q=strtok_r(p, ",", &strtok_next))) { + if (NumDpids < MAXDPIDS) { + ch->dpids[ch->NumDpids++] = + strtol (q, NULL, 10); + } else + // no need to set ok to 'false' + printf ("ERROR: too many DPIDs!" + "\n"); + p = NULL; + } + ch->dpids[ch->NumDpids] = 0; + } +#if 0 + if (caidbuf) { + char *p = caidbuf; + char *q; + int NumCaIds = 0; + char *strtok_next; + while ((q=strtok_r(p, ",", &strtok_next))) { + if (NumCaIds < MAXCAIDS) { + caids[NumCaIds++] = + strtol (q, NULL, 16) & 0xFFFF; + if (NumCaIds == 1 && + caids[0] <= 0x00FF) + break; + } else + // no need to set ok to 'false' + printf ("ERROR: too many CA ids!" + \n"); + p = NULL; + } + caids[NumCaIds] = 0; + } +#endif + } + strreplace (namebuf, '|', ':'); + { + char *p = strchr (namebuf, ';'); + if (p) { + *p++ = 0; + ch->provider = strdup (p); + if (!ch->provider) { + printf ("ERROR: out of memory!\n"); + ok = 0; + } + } + p = strchr (namebuf, ','); + if (p) { + *p++ = 0; + ch->shortName = strdup (p); + if (!ch->shortName) { + printf ("ERROR: out of memory!\n"); + ok = 0; + } + } + } + ch->name = strdup (namebuf); + if (!ch->name) { + printf ("ERROR: out of memory!\n"); + ok = 0; + } + + } else + ok = 0; +#if (defined WIN32 || defined APPLE) + free (parambuf); + free (sourcebuf); + free (vpidbuf); + free (apidbuf); + free (caidbuf); + free (namebuf); +#endif + } + return ok; +} diff --git a/mcast/netcv2dvbip/siparser.c b/mcast/netcv2dvbip/siparser.c new file mode 120000 index 0000000..a2f9177 --- /dev/null +++ b/mcast/netcv2dvbip/siparser.c @@ -0,0 +1 @@ +../common/siparser.c
\ No newline at end of file diff --git a/mcast/netcv2dvbip/stream.c b/mcast/netcv2dvbip/stream.c new file mode 100644 index 0000000..8d57f28 --- /dev/null +++ b/mcast/netcv2dvbip/stream.c @@ -0,0 +1,185 @@ +#include "dvbipstream.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include <winsock2.h> +#endif + +#include <pthread.h> +#include <sys/types.h> + +#include "clist.h" +#include "stream.h" +#include "thread.h" + +#define SLEEPTIME (20*1000) + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +cStream::cStream(int Channum, in_addr_t Addr, int portnum) + : cThread("udp streamer") +{ + handle = 0; + channum = Channum; + addr = Addr; + m_portnum = portnum; +} + +cStream::~cStream(void) +{ +} + +bool cStream::StartStream(in_addr_t bindaddr) +{ + int rc=0; + + peer.sin_family = AF_INET; + peer.sin_port = htons(m_portnum); + peer.sin_addr.s_addr = addr; + + udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (udp_socket < 0) + { + log_socket_error("Stream: socket()"); + return false; + } + +#if 0 + //---------------------- + // Bind the socket. + sockaddr_in m_LocalAddr; + + m_LocalAddr.sin_family = AF_INET; + m_LocalAddr.sin_port = htons(m_portnum-1); + m_LocalAddr.sin_addr.s_addr = bindaddr; + + rc = ::bind( udp_socket, (struct sockaddr*)&m_LocalAddr, + sizeof(m_LocalAddr) ); + if ( rc < 0 ) + { + log_socket_error("STREAM bind()"); + return false; + } +#endif +#if 0 + char loop = 1; + rc = setsockopt(udp_socket, IPPROTO_IP, IP_MULTICAST_LOOP, + (char*)&loop, sizeof(loop)); + if ( rc < 0 ) + { + log_socket_error("STREAM setsockopt(IP_MULTICAST_LOOP)"); + return false; + } +#endif + rc = setsockopt(udp_socket, IPPROTO_IP, IP_MULTICAST_IF, + (char *)&bindaddr, sizeof(bindaddr)); + if ( rc < 0 ) + { + log_socket_error("STREAM setsockopt(IP_MULTICAST_IF)"); + return false; + } + + + pthread_mutex_lock(&lock); + + if (handle == 0) + { + handle = mcli_stream_setup(channum); + if (handle) + Start(); + } + else + { + printf("cStream: handle != NULL !\n"); + } + pthread_mutex_unlock(&lock); + + return true; + +} + +void cStream::Action() +{ + unsigned int retries; + size_t len; + char *ptr; + struct pollfd p; + + p.fd = udp_socket; + p.events = POLLOUT; + + while (Running()) + { + for (retries=1;;retries++) { + if (retries&0xff) + len = mcli_stream_access (handle, &ptr); + else + len = mcli_stream_part_access (handle, &ptr); + if (len) + break; + if(!Running())goto out; + usleep (SLEEPTIME); + } + + switch (poll(&p,1,100)) { + case -1: + log_socket_error( "STREAM poll()" ); + case 0: + usleep (SLEEPTIME); + continue; + default: + if (!(p.revents&POLLOUT)) { + usleep (SLEEPTIME); + continue; + } + if (sendto( udp_socket, ptr, len, 0, + (struct sockaddr *)&peer, sizeof(peer) ) < 0) { +#ifndef WIN32 + if ((errno == EINTR)||(errno == EWOULDBLOCK)) { + usleep (SLEEPTIME); + continue; + } +#else + int rc; + rc = WSAGetLastError(); + if ((rc == WSAEINTR)||(rc == WSAEWOULDBLOCK)) { + usleep (SLEEPTIME); + continue; + } +#endif + log_socket_error("STREAM: sendto()"); + goto out; + } + break; + } + + mcli_stream_skip (handle); + } +out:; +} + +void cStream::StopStream() +{ + Cancel(3); + + pthread_mutex_lock(&lock); + + if (handle) + { + mcli_stream_stop(handle); + handle = 0; + } + else + { + printf("cStream: handle == NULL !\n"); + } + pthread_mutex_unlock(&lock); + +#ifdef WIN32 + closesocket(udp_socket); +#else + close(udp_socket); +#endif + +} + diff --git a/mcast/netcv2dvbip/stream.h b/mcast/netcv2dvbip/stream.h new file mode 100644 index 0000000..1e9464a --- /dev/null +++ b/mcast/netcv2dvbip/stream.h @@ -0,0 +1,31 @@ +#ifndef __STREAM_H +#define __STREAM_H + +#ifdef WIN32 +#include <winsock2.h> +#endif + +#include "clist.h" +#include "thread.h" +#include "misc.h" + +class cStream : public cListObject, public cThread +{ + public: + cStream(int channum, in_addr_t addr, int portnum); + ~cStream(void); + bool StartStream(in_addr_t bindaddr); + void StopStream(); + + private: + void *handle; + SCKT udp_socket; + struct sockaddr_in peer; + int channum; + in_addr_t addr; + int m_portnum; + + virtual void Action(); +}; + +#endif diff --git a/mcast/netcv2dvbip/streamer.c b/mcast/netcv2dvbip/streamer.c new file mode 100644 index 0000000..6403386 --- /dev/null +++ b/mcast/netcv2dvbip/streamer.c @@ -0,0 +1,101 @@ +#include "defs.h" + +#include <stdio.h> + +#include "misc.h" +#include "igmp.h" +#include "streamer.h" +#include "stream.h" + +#define MULTICAST_PRIV_MIN (0xEFFF0000) +#define MULTICAST_PRIV_MAX (0xEFFF1000) + +cStreamer::cStreamer() +{ + m_IgmpMain = NULL; + m_bindaddr = 0; + m_portnum = 0; + m_table = 0; +} + +void cStreamer::SetBindIf(iface_t bindif) +{ + m_bindaddr = bindif.ipaddr; + m_bindif = bindif; +} + +void cStreamer::SetStreamPort(int portnum) +{ + m_portnum = portnum; +} + +void cStreamer::SetTable(int table) +{ + m_table = table; +} + +void cStreamer::Run() +{ + if ( m_IgmpMain == NULL ) + { + m_IgmpMain = new cIgmpMain(this, m_bindif, m_table); + m_IgmpMain->StartListener(); + } + return; +} + +void cStreamer::Stop() +{ + if ( m_IgmpMain ) + { + m_IgmpMain->Destruct(); + delete m_IgmpMain; + m_IgmpMain = NULL; + } + return; +} + +void cStreamer::SetNumGroups(int numgroups) +{ + m_numgroups = numgroups; +} + +bool cStreamer::IsGroupinRange(in_addr_t groupaddr) +{ + in_addr_t g = htonl(groupaddr); + if ( (g > MULTICAST_PRIV_MIN) && (g <= MULTICAST_PRIV_MIN+m_numgroups) + &&(g <= MULTICAST_PRIV_MAX) ) + { + return true; + } + return false; +} + +void cStreamer::StartMulticast(cMulticastGroup* Group) +{ + in_addr group; + group.s_addr = Group->group; + unsigned long channel = htonl(Group->group) - MULTICAST_PRIV_MIN; + + printf("START Channel %d on Multicast Group: %s\n", + (unsigned short) channel, inet_ntoa(group)); + if (Group->stream == NULL) + { + Group->stream = new cStream(channel, Group->group, m_portnum); + Group->stream->StartStream(m_bindaddr); + } +} + +void cStreamer::StopMulticast(cMulticastGroup* Group) +{ + in_addr group; + group.s_addr = Group->group; + + printf("STOP Multicast Group: %s\n", inet_ntoa(group)); + if (Group->stream) + { + Group->stream->StopStream(); + delete Group->stream; + Group->stream = NULL; + } +} diff --git a/mcast/netcv2dvbip/streamer.h b/mcast/netcv2dvbip/streamer.h new file mode 100644 index 0000000..cc9388d --- /dev/null +++ b/mcast/netcv2dvbip/streamer.h @@ -0,0 +1,35 @@ +#ifndef __STREAMER_H +#define __STREAMER_H + +#include "misc.h" + +class cMulticastGroup; +class cIgmpMain; + +class cStreamer +{ + public: + cStreamer(); + + void Run(); + void Stop(); + void SetBindIf(iface_t bindif); + void SetStreamPort(int portnum); + void SetTable(int table); + void SetNumGroups(int numgroups); + + bool IsGroupinRange(in_addr_t groupaddr); + void StartMulticast(cMulticastGroup* Group); + void StopMulticast(cMulticastGroup* Group); + + + private: + cIgmpMain* m_IgmpMain; + in_addr_t m_bindaddr; + iface_t m_bindif; + int m_table; + int m_portnum; + int m_numgroups; +}; + +#endif diff --git a/mcast/netcv2dvbip/thread.c b/mcast/netcv2dvbip/thread.c new file mode 100644 index 0000000..1879ebb --- /dev/null +++ b/mcast/netcv2dvbip/thread.c @@ -0,0 +1,327 @@ +#include "defs.h" + +#include <errno.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#ifndef APPLE +#include <malloc.h> +#endif +#include <pthread.h> +#include <signal.h> +#include <string.h> + +#include "thread.h" +#include "misc.h" + +// --- cCondWait ------------------------------------------------------------- + +cCondWait::cCondWait(void) +{ + signaled = false; + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); +} + +cCondWait::~cCondWait() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); +} + +void cCondWait::SleepMs(int TimeoutMs) +{ + cCondWait w; + // making sure the time is >2ms to avoid a possible busy wait + // (no longer true for later 2.6 kernels) + w.Wait(xmax(TimeoutMs, 3)); +} + +bool cCondWait::Wait(int TimeoutMs) +{ + pthread_mutex_lock(&mutex); + if (!signaled) { + if (TimeoutMs) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + while (!signaled) { + if (pthread_cond_timedwait(&cond, + &mutex, &abstime) == ETIMEDOUT) + break; + } + } + } + else + pthread_cond_wait(&cond, &mutex); + } + bool r = signaled; + signaled = false; + pthread_mutex_unlock(&mutex); + return r; +} + +void cCondWait::Signal(void) +{ + pthread_mutex_lock(&mutex); + signaled = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); +} + +// --- cCondVar -------------------------------------------------------------- + +cCondVar::cCondVar(void) +{ + pthread_cond_init(&cond, 0); +} + +cCondVar::~cCondVar() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); +} + +void cCondVar::Wait(cMutex &Mutex) +{ + if (Mutex.locked) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, + // as pthread_cond_wait + // does an implicit unlock of the mutex + pthread_cond_wait(&cond, &Mutex.mutex); + Mutex.locked = locked; + } +} + +bool cCondVar::TimedWait(cMutex &Mutex, int TimeoutMs) +{ + bool r = true; // true = condition signaled, false = timeout + + if (Mutex.locked) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count + // here, as pthread_cond_timedwait + // does an implicit mutex unlock. + if (pthread_cond_timedwait(&cond, &Mutex.mutex, + &abstime) == ETIMEDOUT) + r = false; + Mutex.locked = locked; + } + } + return r; +} + +void cCondVar::Broadcast(void) +{ + pthread_cond_broadcast(&cond); +} + + + +// --- cMutex ---------------------------------------------------------------- + +cMutex::cMutex(void) +{ + locked = 0; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); +#ifndef APPLE + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP); +#endif + pthread_mutex_init(&mutex, &attr); +} + +cMutex::~cMutex() +{ + pthread_mutex_destroy(&mutex); +} + +void cMutex::Lock(void) +{ + pthread_mutex_lock(&mutex); + locked++; +} + +void cMutex::Unlock(void) +{ + if (!--locked) + pthread_mutex_unlock(&mutex); +} +//---cMutex-------------------------------------------------------------------- + + +cThread::cThread(const char *Description) +{ + active = running = false; +#ifdef WIN32 + memset(&childTid, 0, sizeof(childTid)); +#else + childTid = 0; +#endif + description = NULL; + if (Description) + { + description = strdup(Description); + } +} + +cThread::~cThread() +{ + Cancel(); // just in case the derived class didn't call it + free(description); +} + +void *cThread::StartThread(cThread *Thread) +{ + if (Thread->description && !quiet) + printf("%s thread started.\n", Thread->description); + + Thread->Action(); + if (Thread->description && !quiet) + printf("%s thread ended.\n", Thread->description); + Thread->running = false; + Thread->active = false; + return NULL; +} + +// ms to wait for a thread to stop before newly starting it +#define THREAD_STOP_TIMEOUT 3000 +// ms to sleep while waiting for a thread to stop +#define THREAD_STOP_SLEEP 30 + +bool cThread::Start(void) +{ + if (!running) { + if (active) { + // Wait until the previous incarnation + // of this thread has completely ended + // before starting it newly: + cTimeMs RestartTimeout; + while (!running && active && RestartTimeout.Elapsed() < + THREAD_STOP_TIMEOUT) + cCondWait::SleepMs(THREAD_STOP_SLEEP); + } + if (!active) { + active = running = true; + if (pthread_create(&childTid, NULL, (void *(*) (void *)) + &StartThread, (void *)this) == 0) { + pthread_detach(childTid); // auto-reap + } else { + printf("%s thread cannot be created.\n", + description); + active = running = false; + return false; + } + } + } + return true; +} + + +bool cThread::Active(void) +{ + if (active) { + // + // Single UNIX Spec v2 says: + // + // The pthread_kill() function is used to request + // that a signal be delivered to the specified thread. + // + // As in kill(), if sig is zero, error checking is + // performed but no signal is actually sent. + // + int err; + if ((err = pthread_kill(childTid, 0)) != 0) { + if (err != ESRCH) + printf("%s thread cannot be killed.\n", description); +#ifdef WIN32 + memset(&childTid, 0, sizeof(childTid)); +#else + childTid = 0; +#endif + active = running = false; + } else + return true; + } + return false; +} + +void cThread::Cancel(int WaitSeconds) +{ + running = false; + if (active && WaitSeconds > -1) { + if (WaitSeconds > 0) { + for (time_t t0 = time(NULL) + WaitSeconds; + time(NULL) < t0; ) { + if (!Active()) + return; + cCondWait::SleepMs(10); + } + printf("ERROR: %s thread won't end (waited %d seconds)" + " - canceling it...\n", description ? + description : "", WaitSeconds); + } + pthread_cancel(childTid); +#ifdef WIN32 + memset(&childTid, 0, sizeof(childTid)); +#else + childTid = 0; +#endif + active = false; + } +} + +// --- cMutexLock ------------------------------------------------------------ + +cMutexLock::cMutexLock(cMutex *Mutex) +{ + mutex = NULL; + locked = false; + Lock(Mutex); +} + +cMutexLock::~cMutexLock() +{ + if (mutex && locked) + mutex->Unlock(); +} + +bool cMutexLock::Lock(cMutex *Mutex) +{ + if (Mutex && !mutex) { + mutex = Mutex; + Mutex->Lock(); + locked = true; + return true; + } + return false; +} + +// --- cThreadLock ----------------------------------------------------------- + +cThreadLock::cThreadLock(cThread *Thread) +{ + thread = NULL; + locked = false; + Lock(Thread); +} + +cThreadLock::~cThreadLock() +{ + if (thread && locked) + thread->Unlock(); +} + +bool cThreadLock::Lock(cThread *Thread) +{ + if (Thread && !thread) { + thread = Thread; + Thread->Lock(); + locked = true; + return true; + } + return false; +} diff --git a/mcast/netcv2dvbip/thread.h b/mcast/netcv2dvbip/thread.h new file mode 100644 index 0000000..f33172c --- /dev/null +++ b/mcast/netcv2dvbip/thread.h @@ -0,0 +1,131 @@ +#ifndef __THREAD_H +#define __THREAD_H + +#include <pthread.h> +#include "misc.h" + +class cCondWait { +private: + pthread_mutex_t mutex; + pthread_cond_t cond; + bool signaled; +public: + cCondWait(void); + ~cCondWait(); + static void SleepMs(int TimeoutMs); + ///< Creates a cCondWait object and uses it to sleep for TimeoutMs + ///< milliseconds, immediately giving up the calling thread's time + ///< slice and thus avoiding a "busy wait". + ///< In order to avoid a possible busy wait, TimeoutMs will be + ///< automatically limited to values >2. + bool Wait(int TimeoutMs = 0); + ///< Waits at most TimeoutMs milliseconds for a call to Signal(), or + ///< forever if TimeoutMs is 0. + ///< \return Returns true if Signal() has been called, false it the given + ///< timeout has expired. + void Signal(void); + ///< Signals a caller of Wait() that the condition it is waiting for is + ///< met. +}; + +class cMutex; + +class cCondVar { +private: + pthread_cond_t cond; +public: + cCondVar(void); + ~cCondVar(); + void Wait(cMutex &Mutex); + bool TimedWait(cMutex &Mutex, int TimeoutMs); + void Broadcast(void); +}; + +class cMutex { + friend class cCondVar; +private: + pthread_mutex_t mutex; + int locked; +public: + cMutex(void); + ~cMutex(); + void Lock(void); + void Unlock(void); +}; + +class cThread { +friend class cThreadLock; +private: + cMutex mutex; + bool active; + bool running; + pthread_t childTid; + char *description; + static void *StartThread(cThread *Thread); +protected: + void Lock(void) { mutex.Lock(); } + void Unlock(void) { mutex.Unlock(); } + virtual void Action(void) = 0; + ///< A derived cThread class must implement the code it wants to + ///< execute as a separate thread in this function. If this is + ///< a loop, it must check Running() repeatedly to see whether + ///< it's time to stop. + bool Running(void) { return running; } + ///< Returns false if a derived cThread object shall leave its Action() + ///< function. + void Cancel(int WaitSeconds = 0); + ///< Cancels the thread by first setting 'running' to false, so that + ///< the Action() loop can finish in an orderly fashion and then waiting + ///< up to WaitSeconds seconds for the thread to actually end. If the + ///< thread doesn't end by itself, it is killed. + ///< If WaitSeconds is -1, only 'running' is set to false and Cancel() + ///< returns immediately, without killing the thread. +public: + cThread(const char *Description = NULL); + ///< Creates a new thread. + ///< If Description is present, a log file entry will be made when + ///< the thread starts and stops. The Start() function must be called + ///< to actually start the thread. + virtual ~cThread(); + bool Start(void); + ///< Actually starts the thread. + ///< If the thread is already running, nothing happens. + bool Active(void); + ///< Checks whether the thread is still alive. + }; + +// cMutexLock can be used to easily set a lock on mutex and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which +// uses cMutexLock may itself use a cMutexLock to make one longer lock instead +// of many short ones. + +class cMutexLock { +private: + cMutex *mutex; + bool locked; +public: + cMutexLock(cMutex *Mutex = NULL); + ~cMutexLock(); + bool Lock(cMutex *Mutex); + }; + +// cThreadLock can be used to easily set a lock in a thread and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which +// uses cThreadLock may itself use a cThreadLock to make one longer lock instead +// of many short ones. + +class cThreadLock { +private: + cThread *thread; + bool locked; +public: + cThreadLock(cThread *Thread = NULL); + ~cThreadLock(); + bool Lock(cThread *Thread); + }; + +#define LOCK_THREAD cThreadLock ThreadLock(this) + +#endif |