diff --git a/Makefile b/Makefile index 72086a3..547828b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.4 2007/09/15 23:58:23 rahrenbe Exp $ +# $Id: Makefile,v 1.5 2007/09/16 09:38:00 ajhseppa Exp $ # Debugging on/off #IPTV_DEBUG = 1 @@ -57,7 +57,7 @@ endif ### The object files (add further files here): -OBJS = $(PLUGIN).o config.o setup.o device.o streamer.o protocoludp.o +OBJS = $(PLUGIN).o config.o setup.o device.o streamer.o protocoludp.o protocolhttp.o ### The main target: diff --git a/device.c b/device.c index 7a19e6c..28774ff 100644 --- a/device.c +++ b/device.c @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: device.c,v 1.16 2007/09/15 23:58:23 rahrenbe Exp $ + * $Id: device.c,v 1.17 2007/09/16 09:38:01 ajhseppa Exp $ */ #include "common.h" @@ -33,7 +33,7 @@ cIptvDevice::cIptvDevice(unsigned int Index) //debug("Buffer=%d Prefill=%d\n", MEGABYTE(IptvConfig.GetBufferSizeMB()), tsBufferPrefill); pUdpProtocol = new cIptvProtocolUdp(); //pRtspProtocol = new cIptvProtocolRtsp(); - //pHttpProtocol = new cIptvProtocolHttp(); + pHttpProtocol = new cIptvProtocolHttp(); pIptvStreamer = new cIptvStreamer(tsBuffer, &mutex); StartSectionHandler(); } @@ -88,10 +88,10 @@ cString cIptvDevice::GetChannelSettings(const char *Param, int *IpPort, cIptvPro // *Protocol = pRtspProtocol; // return cString::sprintf("%u.%u.%u.%u", a, b, c, d); // } - //else if (sscanf(Param, "IPTV-HTTP-%u.%u.%u.%u-%u", &a, &b, &c, &d, IpPort) == 5) { - // *Protocol = pHttpProtocol; - // return cString::sprintf("%u.%u.%u.%u", a, b, c, d); - // } + else if (sscanf(Param, "IPTV-HTTP-%u.%u.%u.%u-%u", &a, &b, &c, &d, IpPort) == 5) { + *Protocol = pHttpProtocol; + return cString::sprintf("%u.%u.%u.%u", a, b, c, d); + } return NULL; } diff --git a/device.h b/device.h index 7a57c6d..57d51dc 100644 --- a/device.h +++ b/device.h @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: device.h,v 1.6 2007/09/15 21:27:00 rahrenbe Exp $ + * $Id: device.h,v 1.7 2007/09/16 09:38:01 ajhseppa Exp $ */ #ifndef __IPTV_DEVICE_H @@ -12,7 +12,7 @@ #include #include "protocoludp.h" //#include "protocolrtsp.h" -//#include "protocolhttp.h" +#include "protocolhttp.h" #include "streamer.h" class cIptvDevice : public cDevice { @@ -32,7 +32,7 @@ private: int tsBufferPrefill; cIptvProtocolUdp *pUdpProtocol; //cIptvProtocolRtsp *pRtspProtocol; - //cIptvProtocolHttp *pHttpProtocol; + cIptvProtocolHttp *pHttpProtocol; cIptvStreamer *pIptvStreamer; cMutex mutex; diff --git a/protocolhttp.c b/protocolhttp.c new file mode 100644 index 0000000..afcbcc6 --- /dev/null +++ b/protocolhttp.c @@ -0,0 +1,281 @@ +/* + * protocolhttp.c: IPTV plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: protocolhttp.c,v 1.1 2007/09/16 09:38:01 ajhseppa Exp $ + */ + +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "protocolhttp.h" + +cIptvProtocolHttp::cIptvProtocolHttp() +: streamPort(1234), + socketDesc(-1), + readBufferLen(TS_SIZE * 7), + unicastActive(false) +{ + debug("cIptvProtocolHttp::cIptvProtocolHttp()\n"); + streamAddr = strdup(""); + // Allocate receive buffer + readBuffer = MALLOC(unsigned char, readBufferLen); + if (!readBuffer) + error("ERROR: MALLOC() failed in ProtocolHttp()"); +} + +cIptvProtocolHttp::~cIptvProtocolHttp() +{ + debug("cIptvProtocolHttp::~cIptvProtocolHttp()\n"); + // Close the socket + Close(); + // Free allocated memory + free(streamAddr); + free(readBuffer); +} + +bool cIptvProtocolHttp::OpenSocket(const int Port) +{ + debug("cIptvProtocolHttp::OpenSocket()\n"); + // If socket is there already and it is bound to a different port, it must + // be closed first + if (Port != streamPort) { + debug("cIptvProtocolHttp::OpenSocket(): Socket tear-down\n"); + CloseSocket(); + } + // Bind to the socket if it is not active already + if (socketDesc < 0) { + int yes = 1; + // Create socket + socketDesc = socket(PF_INET, SOCK_STREAM, 0); + if (socketDesc < 0) { + char tmp[64]; + error("ERROR: socket(): %s", strerror_r(errno, tmp, sizeof(tmp))); + return false; + } + + // Make it use non-blocking I/O to avoid stuck read calls + if (fcntl(socketDesc, F_SETFL, O_NONBLOCK)) { + char tmp[64]; + error("ERROR: fcntl(): %s", strerror_r(errno, tmp, sizeof(tmp))); + CloseSocket(); + return false; + } + + // Allow multiple sockets to use the same PORT number + if (setsockopt(socketDesc, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(yes)) < 0) { + char tmp[64]; + error("ERROR: setsockopt(): %s", strerror_r(errno, tmp, sizeof(tmp))); + CloseSocket(); + return false; + } + + // Create default socket + memset(&sockAddr, '\0', sizeof(sockAddr)); + sockAddr.sin_family = AF_INET; + sockAddr.sin_port = htons(Port); + sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); + + // Update stream port + streamPort = Port; + } + return true; +} + +void cIptvProtocolHttp::CloseSocket(void) +{ + debug("cIptvProtocolHttp::CloseSocket()\n"); + // Check if socket exists + if (socketDesc >= 0) { + close(socketDesc); + socketDesc = -1; + } +} + +bool cIptvProtocolHttp::Connect(void) +{ + debug("cIptvProtocolHttp::Connect()\n"); + // Check that stream address is valid + if (!unicastActive && !isempty(streamAddr)) { + // Ensure that socket is valid + OpenSocket(streamPort); + + // First try only the IP -address + sockAddr.sin_addr.s_addr = inet_addr(streamAddr); + + if (sockAddr.sin_addr.s_addr == INADDR_NONE) { + debug("Cannot convert %s directly to internet address\n", streamAddr); + + // It may be a host name, get the name + struct hostent *host; + host = gethostbyname(streamAddr); + if (!host) { + error("%s is not valid address\n", streamAddr); + char tmp[64]; + error("ERROR: %s", strerror_r(h_errno, tmp, sizeof(tmp))); + return false; + } + + sockAddr.sin_addr.s_addr = inet_addr(*host->h_addr_list); + } + + int err = connect(socketDesc, (struct sockaddr*)&sockAddr, + sizeof(sockAddr)); + // Non-blocking sockets always report in-progress error when connected + if (err < 0 && errno != EINPROGRESS) { + char tmp[64]; + error("ERROR: Connect(): %s", strerror_r(errno, tmp, sizeof(tmp))); + return false; + } + + // Select on the socket completion + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + // Use select to check socket writability + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(socketDesc, &wfds); + int retval = select(socketDesc + 1, NULL, &wfds, NULL, &tv); + // Check if error + if (retval < 0) { + char tmp[64]; + error("ERROR: select(): %s", strerror_r(errno, tmp, sizeof(tmp))); + return -1; + } + + // Select has returned. Get socket errors if there are any + int socketStatus = 0; + socklen_t len = sizeof(socketStatus); + getsockopt(socketDesc, SOL_SOCKET, SO_ERROR, &socketStatus, &len); + + // If not any errors, then socket must be ready and connected + if (socketStatus != 0) { + error("Cannot connect to %s\n", streamAddr); + char tmp[64]; + error("ERROR: %s", strerror_r(socketStatus, tmp, sizeof(tmp))); + return false; + } + + // Formulate and send HTTP -request + char buffer[256]; + memset(buffer, '\0', sizeof(buffer)); + snprintf(buffer, sizeof(buffer), + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: vdr-iptv\r\n" + "Range: bytes=0-\r\n" + "Connection: Close\r\n" + "\r\n", "/", streamAddr); // Hardcoding of file name must go! + + debug("Sending http -request: %s\n", buffer); + + err = send(socketDesc, buffer, strlen(buffer), 0); + if (err < 0) { + char tmp[64]; + error("ERROR: send(): %s", strerror_r(errno, tmp, sizeof(tmp))); + return false; + } + + // It would be a good idea to wait here for the reception to begin instead + // of blindly assuming that datastream is really active. + + // Also parsing the reply headers should happen here to see if the + // connection should be re-located etc. + + + // Update active flag + unicastActive = true; + } + return true; +} + +bool cIptvProtocolHttp::Disconnect(void) +{ + debug("cIptvProtocolHttp::Disconnect()\n"); + // Check that stream address is valid + if (unicastActive && !isempty(streamAddr)) { + // Ensure that socket is valid + OpenSocket(streamPort); + + int err = close(socketDesc); + if (err < 0) { + char tmp[64]; + error("ERROR: close(): %s", strerror_r(errno, tmp, sizeof(tmp))); + return false; + } + // Update active flag + unicastActive = false; + } + return true; +} + +int cIptvProtocolHttp::Read(unsigned char* *BufferAddr) +{ + //debug("cIptvProtocolHttp::Read()\n"); + socklen_t addrlen = sizeof(sockAddr); + // Set argument point to read buffer + *BufferAddr = readBuffer; + // Wait for data + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 500000; + // Use select + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(socketDesc, &rfds); + int retval = select(socketDesc + 1, &rfds, NULL, NULL, &tv); + // Check if error + if (retval < 0) { + char tmp[64]; + error("ERROR: select(): %s", strerror_r(errno, tmp, sizeof(tmp))); + return -1; + } + // Check if data available + else if (retval) { + // Read data from socket + return recvfrom(socketDesc, readBuffer, readBufferLen, MSG_DONTWAIT, + (struct sockaddr *)&sockAddr, &addrlen); + } + return 0; +} + +bool cIptvProtocolHttp::Open(void) +{ + debug("cIptvProtocolHttp::Open(): streamAddr=%s\n", streamAddr); + // Connect the socket + return Connect(); +} + +bool cIptvProtocolHttp::Close(void) +{ + debug("cIptvProtocolHttp::Close(): streamAddr=%s\n", streamAddr); + // Disconnect the current stream + Disconnect(); + // Close the socket + CloseSocket(); + return true; +} + +bool cIptvProtocolHttp::Set(const char* Address, const int Port) +{ + debug("cIptvProtocolHttp::Set(): %s:%d\n", Address, Port); + if (!isempty(Address)) { + // Disconnect the current socket + Disconnect(); + // Update stream address and port + streamAddr = strcpyrealloc(streamAddr, Address); + streamPort = Port; + // Re-connect the socket + Connect(); + } + return true; +} diff --git a/protocolhttp.h b/protocolhttp.h new file mode 100644 index 0000000..1bd5c43 --- /dev/null +++ b/protocolhttp.h @@ -0,0 +1,41 @@ +/* + * protocolhttp.h: IPTV plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: protocolhttp.h,v 1.1 2007/09/16 09:38:01 ajhseppa Exp $ + */ + +#ifndef __IPTV_PROTOCOLHTTP_H +#define __IPTV_PROTOCOLHTTP_H + +#include +#include "protocolif.h" + +class cIptvProtocolHttp : public cIptvProtocolIf { +private: + char* streamAddr; + int streamPort; + int socketDesc; + unsigned char* readBuffer; + unsigned int readBufferLen; + struct sockaddr_in sockAddr; + bool unicastActive; + +private: + bool OpenSocket(const int Port); + void CloseSocket(void); + bool Connect(void); + bool Disconnect(void); + +public: + cIptvProtocolHttp(); + virtual ~cIptvProtocolHttp(); + virtual int Read(unsigned char* *BufferAddr); + virtual bool Set(const char* Address, const int Port); + virtual bool Open(void); + virtual bool Close(void); +}; + +#endif // __IPTV_PROTOCOLHTTP_H +