/* * protocoludp.c: IPTV plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. * * $Id: protocoludp.c,v 1.17 2007/10/20 23:16:28 ajhseppa Exp $ */ #include #include #include #include #include #include #include "common.h" #include "config.h" #include "protocoludp.h" cIptvProtocolUdp::cIptvProtocolUdp() : streamPort(1234), socketDesc(-1), readBufferLen(TS_SIZE * IptvConfig.GetReadBufferTsCount()), isActive(false) { debug("cIptvProtocolUdp::cIptvProtocolUdp()\n"); streamAddr = strdup(""); // Allocate receive buffer readBuffer = MALLOC(unsigned char, readBufferLen); if (!readBuffer) error("ERROR: MALLOC() failed in ProtocolUdp()"); } cIptvProtocolUdp::~cIptvProtocolUdp() { debug("cIptvProtocolUdp::~cIptvProtocolUdp()\n"); // Drop the multicast group and close the socket Close(); // Free allocated memory free(streamAddr); free(readBuffer); } bool cIptvProtocolUdp::OpenSocket(const int Port) { debug("cIptvProtocolUdp::OpenSocket()\n"); // If socket is there already and it is bound to a different port, it must // be closed first if (Port != streamPort) { debug("cIptvProtocolUdp::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_DGRAM, 0); ERROR_IF(socketDesc < 0, "socket()", return false); // Make it use non-blocking I/O to avoid stuck read calls ERROR_IF_FUNC(fcntl(socketDesc, F_SETFL, O_NONBLOCK), "fcntl()", CloseSocket(), return false); // Allow multiple sockets to use the same PORT number ERROR_IF_FUNC(setsockopt(socketDesc, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0, "setsockopt()", CloseSocket(), return false); // Bind socket memset(&sockAddr, '\0', sizeof(sockAddr)); sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(Port); sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); int err = bind(socketDesc, (struct sockaddr *)&sockAddr, sizeof(sockAddr)); ERROR_IF_FUNC(err < 0, "bind()", CloseSocket(), return false); // Update stream port streamPort = Port; } return true; } void cIptvProtocolUdp::CloseSocket(void) { debug("cIptvProtocolUdp::CloseSocket()\n"); // Check if socket exists if (socketDesc >= 0) { close(socketDesc); socketDesc = -1; } } bool cIptvProtocolUdp::JoinMulticast(void) { debug("cIptvProtocolUdp::JoinMulticast()\n"); // Check that stream address is valid if (!isActive && !isempty(streamAddr)) { // Ensure that socket is valid OpenSocket(streamPort); // Join a new multicast group struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(streamAddr); mreq.imr_interface.s_addr = htonl(INADDR_ANY); int err = setsockopt(socketDesc, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); ERROR_IF(err < 0, "setsockopt()", return false); // Update multicasting flag isActive = true; } return true; } bool cIptvProtocolUdp::DropMulticast(void) { debug("cIptvProtocolUdp::DropMulticast()\n"); // Check that stream address is valid if (isActive && !isempty(streamAddr)) { // Ensure that socket is valid OpenSocket(streamPort); // Drop the multicast group struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(streamAddr); mreq.imr_interface.s_addr = htonl(INADDR_ANY); int err = setsockopt(socketDesc, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); ERROR_IF(err < 0, "setsockopt()", return false); // Update multicasting flag isActive = false; } return true; } int cIptvProtocolUdp::Read(unsigned char* *BufferAddr) { //debug("cIptvProtocolUdp::Read()\n"); // Error out if socket not initialized if (socketDesc <= 0) { error("ERROR: Invalid socket in %s\n", __FUNCTION__); return -1; } socklen_t addrlen = sizeof(sockAddr); // Set argument point to read buffer *BufferAddr = readBuffer; // Wait for data int retval = select_single_desc(socketDesc, 500000, false); // Check if error if (retval < 0) return retval; // Check if data available else if (retval) { int len = 0; // Read data from socket if (isActive) len = recvfrom(socketDesc, readBuffer, readBufferLen, MSG_DONTWAIT, (struct sockaddr *)&sockAddr, &addrlen); ERROR_IF(len < 0, "recvfrom()", return len); if ((len > 0) && (readBuffer[0] == 0x47)) { // Set argument point to read buffer *BufferAddr = &readBuffer[0]; return len; } else if (len > 3) { // http://www.networksorcery.com/enp/rfc/rfc2250.txt // version unsigned int v = (readBuffer[0] >> 6) & 0x03; // extension bit unsigned int x = (readBuffer[0] >> 4) & 0x01; // cscr count unsigned int cc = readBuffer[0] & 0x0F; // payload type unsigned int pt = readBuffer[1] & 0x7F; // header lenght unsigned int headerlen = (3 + cc) * sizeof(uint32_t); // check if extension if (x) { // extension header length unsigned int ehl = (((readBuffer[headerlen + 2] & 0xFF) << 8) | (readBuffer[headerlen + 3] & 0xFF)); // update header length headerlen += (ehl + 1) * sizeof(uint32_t); } // Check that rtp is version 2, payload type is MPEG2 TS // and payload contains multiple of TS packet data if ((v == 2) && (pt == 33) && (((len - headerlen) % TS_SIZE) == 0)) { // Set argument point to payload in read buffer *BufferAddr = &readBuffer[headerlen]; return (len - headerlen); } } } return 0; } bool cIptvProtocolUdp::Open(void) { debug("cIptvProtocolUdp::Open()\n"); // Join a new multicast group JoinMulticast(); return true; } bool cIptvProtocolUdp::Close(void) { debug("cIptvProtocolUdp::Close()\n"); // Drop the multicast group DropMulticast(); // Close the socket CloseSocket(); return true; } bool cIptvProtocolUdp::Set(const char* Location, const int Parameter, const int Index) { debug("cIptvProtocolUdp::Set(): Location=%s Parameter=%d Index=%d\n", Location, Parameter, Index); if (!isempty(Location)) { // Drop the multicast group DropMulticast(); // Update stream address and port streamAddr = strcpyrealloc(streamAddr, Location); streamPort = Parameter; // Join a new multicast group JoinMulticast(); } return true; } cString cIptvProtocolUdp::GetInformation(void) { //debug("cIptvProtocolUdp::GetInformation()"); return cString::sprintf("udp://%s:%d", streamAddr, streamPort); }