From 5f5d2965cf117b223d72203fec0b84c0215f6b0a Mon Sep 17 00:00:00 2001 From: Dennis Hamester Date: Wed, 5 Oct 2016 10:45:05 +0200 Subject: [PATCH] Reimplementation with custom rtsp client --- CMakeLists.txt | 4 +- src/OctonetData.cpp | 1 - src/Socket.cpp | 722 ++++++++++++++++++++++++++++++++++++++++++++ src/Socket.h | 305 +++++++++++++++++++ src/client.cpp | 23 +- src/rtsp_client.cpp | 374 +++++++++++++++++++++++ src/rtsp_client.hpp | 10 + 7 files changed, 1433 insertions(+), 6 deletions(-) create mode 100644 src/Socket.cpp create mode 100644 src/Socket.h create mode 100644 src/rtsp_client.cpp create mode 100644 src/rtsp_client.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2075856..ba64e42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,9 @@ set(DEPLIBS set(OCTONET_SOURCES src/OctonetData.cpp - src/client.cpp) + src/client.cpp + src/Socket.cpp + src/rtsp_client.cpp) build_addon(pvr.octonet OCTONET DEPLIBS) diff --git a/src/OctonetData.cpp b/src/OctonetData.cpp index 612f153..b102f7d 100644 --- a/src/OctonetData.cpp +++ b/src/OctonetData.cpp @@ -226,7 +226,6 @@ PVR_ERROR OctonetData::getChannels(ADDON_HANDLE handle, bool bRadio) chan.bIsRadio = channel.radio; chan.iChannelNumber = i; strncpy(chan.strChannelName, channel.name.c_str(), strlen(channel.name.c_str())); - strncpy(chan.strStreamURL, channel.url.c_str(), strlen(channel.url.c_str())); strcpy(chan.strInputFormat, "video/x-mpegts"); chan.bIsHidden = false; diff --git a/src/Socket.cpp b/src/Socket.cpp new file mode 100644 index 0000000..bcc8980 --- /dev/null +++ b/src/Socket.cpp @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2005-2011 Team XBMC + * http://www.xbmc.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "libXBMC_addon.h" +#include +#include "p8-platform/os.h" +#include "client.h" +#include "Socket.h" +#include + +using namespace std; +using namespace ADDON; + +namespace OCTO +{ + +/* Master defines for client control */ +#define RECEIVE_TIMEOUT 6 //sec + +Socket::Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol) +{ + _sd = INVALID_SOCKET; + _family = family; + _domain = domain; + _type = type; + _protocol = protocol; + _port = 0; + memset (&_sockaddr, 0, sizeof( _sockaddr ) ); +} + + +Socket::Socket() +{ + // Default constructor, default settings + _sd = INVALID_SOCKET; + _family = af_inet; + _domain = pf_inet; + _type = sock_stream; + _protocol = tcp; + _port = 0; + memset (&_sockaddr, 0, sizeof( _sockaddr ) ); +} + + +Socket::~Socket() +{ + close(); + osCleanup(); +} + +bool Socket::setHostname(const std::string& host) +{ + _hostname = host; + return true; +} + +bool Socket::close() +{ + if (is_valid()) + { + if (_sd != SOCKET_ERROR) + closesocket(_sd); + _sd = INVALID_SOCKET; + return true; + } + return false; +} + +bool Socket::create() +{ + close(); + + if(!osInit()) + { + return false; + } + + return true; +} + + +bool Socket::bind ( const unsigned short port ) +{ + + if (is_valid()) + { + close(); + } + + _sd = socket(_family, _type, _protocol); + _port = port; + _sockaddr.sin_family = (sa_family_t) _family; + _sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all + _sockaddr.sin_port = htons( _port ); + + int bind_return = ::bind(_sd, (sockaddr*)(&_sockaddr), sizeof(_sockaddr)); + + if ( bind_return == -1 ) + { + errormessage( getLastError(), "Socket::bind" ); + return false; + } + + return true; +} + + +bool Socket::listen() const +{ + + if (!is_valid()) + { + return false; + } + + int listen_return = ::listen (_sd, SOMAXCONN); + //This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h. + //linux 128//MAXCONNECTIONS =1 + + if (listen_return == -1) + { + errormessage( getLastError(), "Socket::listen" ); + return false; + } + + return true; +} + + +bool Socket::accept ( Socket& new_socket ) const +{ + if (!is_valid()) + { + return false; + } + + socklen_t addr_length = sizeof( _sockaddr ); + new_socket._sd = ::accept(_sd, const_cast( (const sockaddr*) &_sockaddr), &addr_length ); + +#ifdef TARGET_WINDOWS + if (new_socket._sd == INVALID_SOCKET) +#else + if (new_socket._sd <= 0) +#endif + { + errormessage( getLastError(), "Socket::accept" ); + return false; + } + + return true; +} + + +int Socket::send ( const std::string& data ) +{ + return Socket::send( (const char*) data.c_str(), (const unsigned int) data.size()); +} + + +int Socket::send ( const char* data, const unsigned int len ) +{ + fd_set set_w, set_e; + struct timeval tv; + int result; + + if (!is_valid()) + { + return 0; + } + + // fill with new data + tv.tv_sec = 0; + tv.tv_usec = 0; + + FD_ZERO(&set_w); + FD_ZERO(&set_e); + FD_SET(_sd, &set_w); + FD_SET(_sd, &set_e); + + result = select(FD_SETSIZE, &set_w, NULL, &set_e, &tv); + + if (result < 0) + { + kodi->Log(LOG_ERROR, "Socket::send - select failed"); + close(); + return 0; + } + if (FD_ISSET(_sd, &set_w)) + { + kodi->Log(LOG_ERROR, "Socket::send - failed to send data"); + close(); + return 0; + } + + int status = ::send(_sd, data, len, 0 ); + + if (status == -1) + { + errormessage( getLastError(), "Socket::send"); + kodi->Log(LOG_ERROR, "Socket::send - failed to send data"); + close(); + return 0; + } + return status; +} + + +int Socket::sendto ( const char* data, unsigned int size, bool sendcompletebuffer) +{ + int sentbytes = 0; + int i; + + do + { + i = ::sendto(_sd, data, size, 0, (const struct sockaddr*) &_sockaddr, sizeof( _sockaddr ) ); + + if (i <= 0) + { + errormessage( getLastError(), "Socket::sendto"); + osCleanup(); + return i; + } + sentbytes += i; + } while ( (sentbytes < (int) size) && (sendcompletebuffer == true)); + + return i; +} + + +int Socket::receive ( std::string& data, unsigned int minpacketsize ) const +{ + char * buf = NULL; + int status = 0; + + if (!is_valid()) + { + return 0; + } + + buf = new char [ minpacketsize + 1 ]; + memset ( buf, 0, minpacketsize + 1 ); + + status = receive( buf, minpacketsize, minpacketsize ); + + data = buf; + + delete[] buf; + return status; +} + + +//Receive until error or \n +bool Socket::ReadLine (string& line) +{ + fd_set set_r, set_e; + timeval timeout; + int retries = 6; + char buffer[2048]; + + if (!is_valid()) + return false; + + while (true) + { + size_t pos1 = line.find("\r\n", 0); + if (pos1 != std::string::npos) + { + line.erase(pos1, string::npos); + return true; + } + + timeout.tv_sec = RECEIVE_TIMEOUT; + timeout.tv_usec = 0; + + // fill with new data + FD_ZERO(&set_r); + FD_ZERO(&set_e); + FD_SET(_sd, &set_r); + FD_SET(_sd, &set_e); + int result = select(FD_SETSIZE, &set_r, NULL, &set_e, &timeout); + + if (result < 0) + { + kodi->Log(LOG_DEBUG, "%s: select failed", __FUNCTION__); + errormessage(getLastError(), __FUNCTION__); + close(); + return false; + } + + if (result == 0) + { + if (retries != 0) + { + kodi->Log(LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __FUNCTION__, retries); + retries--; + continue; + } else { + kodi->Log(LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", __FUNCTION__); + return false; + } + } + + result = recv(_sd, buffer, sizeof(buffer) - 1, 0); + if (result < 0) + { + kodi->Log(LOG_DEBUG, "%s: recv failed", __FUNCTION__); + errormessage(getLastError(), __FUNCTION__); + close(); + return false; + } + buffer[result] = 0; + + line.append(buffer); + } + + return true; +} + + +int Socket::receive ( std::string& data) const +{ + char buf[MAXRECV + 1]; + int status = 0; + + if ( !is_valid() ) + { + return 0; + } + + memset ( buf, 0, MAXRECV + 1 ); + status = receive( buf, MAXRECV, 0 ); + data = buf; + + return status; +} + +int Socket::receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const +{ + unsigned int receivedsize = 0; + + if ( !is_valid() ) + { + return 0; + } + + while ( (receivedsize <= minpacketsize) && (receivedsize < buffersize) ) + { + int status = ::recv(_sd, data+receivedsize, (buffersize - receivedsize), 0 ); + + if ( status == SOCKET_ERROR ) + { + errormessage( getLastError(), "Socket::receive" ); + return status; + } + + receivedsize += status; + } + + return receivedsize; +} + + +int Socket::recvfrom ( char* data, const int buffersize, struct sockaddr* from, socklen_t* fromlen) const +{ + int status = ::recvfrom(_sd, data, buffersize, 0, from, fromlen); + + return status; +} + + +bool Socket::connect ( const std::string& host, const unsigned short port ) +{ + close(); + + if ( !setHostname( host ) ) + { + kodi->Log(LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str()); + return false; + } + _port = port; + + char strPort[15]; + snprintf(strPort, 15, "%hu", port); + + struct addrinfo hints; + struct addrinfo* result = NULL; + struct addrinfo *address = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = _family; + hints.ai_socktype = _type; + hints.ai_protocol = _protocol; + + int retval = getaddrinfo(host.c_str(), strPort, &hints, &result); + if (retval != 0) + { + errormessage(getLastError(), "Socket::connect"); + return false; + } + + for (address = result; address != NULL; address = address->ai_next) + { + // Create the socket + _sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); + + if (_sd == INVALID_SOCKET) + { + errormessage(getLastError(), "Socket::create"); + continue; + } + + int status = ::connect(_sd, address->ai_addr, address->ai_addrlen); + if (status == SOCKET_ERROR) + { + close(); + continue; + } + + // We have a conection + break; + } + + freeaddrinfo(result); + + if (address == NULL) + { + kodi->Log(LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port); + errormessage(getLastError(), "Socket::connect"); + close(); + return false; + } + + return true; +} + +bool Socket::reconnect() +{ + if ( is_valid() ) + { + return true; + } + + return connect(_hostname, _port); +} + +bool Socket::is_valid() const +{ + return (_sd != INVALID_SOCKET); +} + +#if defined(TARGET_WINDOWS) +bool Socket::set_non_blocking ( const bool b ) +{ + u_long iMode; + + if ( b ) + iMode = 1; // enable non_blocking + else + iMode = 0; // disable non_blocking + + if (ioctlsocket(_sd, FIONBIO, &iMode) == -1) + { + kodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i", iMode); + return false; + } + + return true; +} + +void Socket::errormessage( int errnum, const char* functionname) const +{ + const char* errmsg = NULL; + + switch (errnum) + { + case WSANOTINITIALISED: + errmsg = "A successful WSAStartup call must occur before using this function."; + break; + case WSAENETDOWN: + errmsg = "The network subsystem or the associated service provider has failed"; + break; + case WSA_NOT_ENOUGH_MEMORY: + errmsg = "Insufficient memory available"; + break; + case WSA_INVALID_PARAMETER: + errmsg = "One or more parameters are invalid"; + break; + case WSA_OPERATION_ABORTED: + errmsg = "Overlapped operation aborted"; + break; + case WSAEINTR: + errmsg = "Interrupted function call"; + break; + case WSAEBADF: + errmsg = "File handle is not valid"; + break; + case WSAEACCES: + errmsg = "Permission denied"; + break; + case WSAEFAULT: + errmsg = "Bad address"; + break; + case WSAEINVAL: + errmsg = "Invalid argument"; + break; + case WSAENOTSOCK: + errmsg = "Socket operation on nonsocket"; + break; + case WSAEDESTADDRREQ: + errmsg = "Destination address required"; + break; + case WSAEMSGSIZE: + errmsg = "Message too long"; + break; + case WSAEPROTOTYPE: + errmsg = "Protocol wrong type for socket"; + break; + case WSAENOPROTOOPT: + errmsg = "Bad protocol option"; + break; + case WSAEPFNOSUPPORT: + errmsg = "Protocol family not supported"; + break; + case WSAEAFNOSUPPORT: + errmsg = "Address family not supported by protocol family"; + break; + case WSAEADDRINUSE: + errmsg = "Address already in use"; + break; + case WSAECONNRESET: + errmsg = "Connection reset by peer"; + break; + case WSAHOST_NOT_FOUND: + errmsg = "Authoritative answer host not found"; + break; + case WSATRY_AGAIN: + errmsg = "Nonauthoritative host not found, or server failure"; + break; + case WSAEISCONN: + errmsg = "Socket is already connected"; + break; + case WSAETIMEDOUT: + errmsg = "Connection timed out"; + break; + case WSAECONNREFUSED: + errmsg = "Connection refused"; + break; + case WSANO_DATA: + errmsg = "Valid name, no data record of requested type"; + break; + default: + errmsg = "WSA Error"; + } + kodi->Log(LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg); +} + +int Socket::getLastError() const +{ + return WSAGetLastError(); +} + +int Socket::win_usage_count = 0; //Declared static in Socket class + +bool Socket::osInit() +{ + win_usage_count++; + // initialize winsock: + if (WSAStartup(MAKEWORD(2,2),&_wsaData) != 0) + { + return false; + } + + WORD wVersionRequested = MAKEWORD(2,2); + + // check version + if (_wsaData.wVersion != wVersionRequested) + { + return false; + } + + return true; +} + +void Socket::osCleanup() +{ + win_usage_count--; + if(win_usage_count == 0) + { + WSACleanup(); + } +} + +#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD +bool Socket::set_non_blocking ( const bool b ) +{ + int opts; + + opts = fcntl(_sd, F_GETFL); + + if ( opts < 0 ) + { + return false; + } + + if ( b ) + opts = ( opts | O_NONBLOCK ); + else + opts = ( opts & ~O_NONBLOCK ); + + if(fcntl (_sd , F_SETFL, opts) == -1) + { + kodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts); + return false; + } + return true; +} + +void Socket::errormessage( int errnum, const char* functionname) const +{ + const char* errmsg = NULL; + + switch ( errnum ) + { + case EAGAIN: //same as EWOULDBLOCK + errmsg = "EAGAIN: The socket is marked non-blocking and the requested operation would block"; + break; + case EBADF: + errmsg = "EBADF: An invalid descriptor was specified"; + break; + case ECONNRESET: + errmsg = "ECONNRESET: Connection reset by peer"; + break; + case EDESTADDRREQ: + errmsg = "EDESTADDRREQ: The socket is not in connection mode and no peer address is set"; + break; + case EFAULT: + errmsg = "EFAULT: An invalid userspace address was specified for a parameter"; + break; + case EINTR: + errmsg = "EINTR: A signal occurred before data was transmitted"; + break; + case EINVAL: + errmsg = "EINVAL: Invalid argument passed"; + break; + case ENOTSOCK: + errmsg = "ENOTSOCK: The argument is not a valid socket"; + break; + case EMSGSIZE: + errmsg = "EMSGSIZE: The socket requires that message be sent atomically, and the size of the message to be sent made this impossible"; + break; + case ENOBUFS: + errmsg = "ENOBUFS: The output queue for a network interface was full"; + break; + case ENOMEM: + errmsg = "ENOMEM: No memory available"; + break; + case EPIPE: + errmsg = "EPIPE: The local end has been shut down on a connection oriented socket"; + break; + case EPROTONOSUPPORT: + errmsg = "EPROTONOSUPPORT: The protocol type or the specified protocol is not supported within this domain"; + break; + case EAFNOSUPPORT: + errmsg = "EAFNOSUPPORT: The implementation does not support the specified address family"; + break; + case ENFILE: + errmsg = "ENFILE: Not enough kernel memory to allocate a new socket structure"; + break; + case EMFILE: + errmsg = "EMFILE: Process file table overflow"; + break; + case EACCES: + errmsg = "EACCES: Permission to create a socket of the specified type and/or protocol is denied"; + break; + case ECONNREFUSED: + errmsg = "ECONNREFUSED: A remote host refused to allow the network connection (typically because it is not running the requested service)"; + break; + case ENOTCONN: + errmsg = "ENOTCONN: The socket is associated with a connection-oriented protocol and has not been connected"; + break; + //case E: + // errmsg = ""; + // break; + default: + break; + } + + kodi->Log(LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg); +} + +int Socket::getLastError() const +{ + return errno; +} + +bool Socket::osInit() +{ + // Not needed for Linux + return true; +} + +void Socket::osCleanup() +{ + // Not needed for Linux +} +#endif //TARGET_WINDOWS || TARGET_LINUX || TARGET_DARWIN || TARGET_FREEBSD + +} //namespace OCTO diff --git a/src/Socket.h b/src/Socket.h new file mode 100644 index 0000000..4d19e40 --- /dev/null +++ b/src/Socket.h @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2005-2011 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once + +//Include platform specific datatypes, header files, defines and constants: +#if defined TARGET_WINDOWS + #define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support + #pragma warning(disable:4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition" + #include + #include + #pragma warning(default:4005) + #include + + #ifndef NI_MAXHOST + #define NI_MAXHOST 1025 + #endif + + #ifndef socklen_t + typedef int socklen_t; + #endif + #ifndef ipaddr_t + typedef unsigned long ipaddr_t; + #endif + #ifndef port_t + typedef unsigned short port_t; + #endif + #ifndef sa_family_t + #define sa_family_t ADDRESS_FAMILY + #endif +#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD +#ifdef SOCKADDR_IN +#undef SOCKADDR_IN +#endif + #include /* for socket,connect */ + #include /* for socket,connect */ + #include /* for Unix socket */ + #include /* for inet_pton */ + #include /* for gethostbyname */ + #include /* for htons */ + #include /* for read, write, close */ + #include + #include + + typedef int SOCKET; + typedef sockaddr SOCKADDR; + typedef sockaddr_in SOCKADDR_IN; + #ifndef INVALID_SOCKET + #define INVALID_SOCKET (-1) + #endif + #define SOCKET_ERROR (-1) + + #define closesocket(sd) ::close(sd) +#else + #error Platform specific socket support is not yet available on this platform! +#endif + + +#include + +namespace OCTO +{ + +#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused" +#define MAXRECV 1500 ///< Maximum packet size + +enum SocketFamily +{ + af_unspec = AF_UNSPEC, + af_inet = AF_INET, + af_inet6 = AF_INET6 +}; + +enum SocketDomain +{ + #if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD + pf_unix = PF_UNIX, + pf_local = PF_LOCAL, + #endif + pf_inet = PF_INET +}; + +enum SocketType +{ + sock_stream = SOCK_STREAM, + sock_dgram = SOCK_DGRAM +}; + +enum SocketProtocol +{ + tcp = IPPROTO_TCP, + udp = IPPROTO_UDP +}; + +class Socket +{ + public: + + /*! + * An unconnected socket may be created directly on the local + * machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and + * protocol may also be specified. + * If the socket cannot be created, an exception is thrown. + * + * \param family Socket family (IPv4 or IPv6) + * \param domain The domain parameter specifies a communications domain within which communication will take place; + * this selects the protocol family which should be used. + * \param type base type and protocol family of the socket. + * \param protocol specific protocol to apply. + */ + Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol = tcp); + Socket(void); + virtual ~Socket(); + + //Socket settings + + /*! + * Socket setFamily + * \param family Can be af_inet or af_inet6. Default: af_inet + */ + void setFamily(const enum SocketFamily family) + { + _family = family; + }; + + /*! + * Socket setDomain + * \param domain Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet + */ + void setDomain(const enum SocketDomain domain) + { + _domain = domain; + }; + + /*! + * Socket setType + * \param type Can be sock_stream or sock_dgram. Default: sock_stream. + */ + void setType(const enum SocketType type) + { + _type = type; + }; + + /*! + * Socket setProtocol + * \param protocol Can be tcp or udp. Default: tcp. + */ + void setProtocol(const enum SocketProtocol protocol) + { + _protocol = protocol; + }; + + /*! + * Socket setPort + * \param port port number for socket communication + */ + void setPort (const unsigned short port) + { + _sockaddr.sin_port = htons ( port ); + }; + + bool setHostname ( const std::string& host ); + + // Server initialization + + /*! + * Socket create + * Create a new socket + * \return True if succesful + */ + bool create(); + + /*! + * Socket close + * Close the socket + * \return True if succesful + */ + bool close(); + + /*! + * Socket bind + */ + bool bind ( const unsigned short port ); + bool listen() const; + bool accept ( Socket& socket ) const; + + // Client initialization + bool connect ( const std::string& host, const unsigned short port ); + + bool reconnect(); + + // Data Transmission + + /*! + * Socket send function + * + * \param data Reference to a std::string with the data to transmit + * \return Number of bytes send or -1 in case of an error + */ + int send ( const std::string& data ); + + /*! + * Socket send function + * + * \param data Pointer to a character array of size 'size' with the data to transmit + * \param size Length of the data to transmit + * \return Number of bytes send or -1 in case of an error + */ + int send ( const char* data, const unsigned int size ); + + /*! + * Socket sendto function + * + * \param data Reference to a std::string with the data to transmit + * \param size Length of the data to transmit + * \param sendcompletebuffer If 'true': do not return until the complete buffer is transmitted + * \return Number of bytes send or -1 in case of an error + */ + int sendto ( const char* data, unsigned int size, bool sendcompletebuffer = false); + // Data Receive + + /*! + * Socket receive function + * + * \param data Reference to a std::string for storage of the received data. + * \param minpacketsize The minimum number of bytes that should be received before returning from this function + * \return Number of bytes received or SOCKET_ERROR + */ + int receive ( std::string& data, unsigned int minpacketsize ) const; + + /*! + * Socket receive function + * + * \param data Reference to a std::string for storage of the received data. + * \return Number of bytes received or SOCKET_ERROR + */ + int receive ( std::string& data ) const; + + /*! + * Socket receive function + * + * \param data Pointer to a character array of size buffersize. Used to store the received data. + * \param buffersize Size of the 'data' buffer + * \param minpacketsize Specifies the minimum number of bytes that need to be received before returning + * \return Number of bytes received or SOCKET_ERROR + */ + int receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const; + + /*! + * Socket recvfrom function + * + * \param data Pointer to a character array of size buffersize. Used to store the received data. + * \param buffersize Size of the 'data' buffer + * \param from Optional: pointer to a sockaddr struct that will get the address from which the data is received + * \param fromlen Optional, only required if 'from' is given: length of from struct + * \return Number of bytes received or SOCKET_ERROR + */ + int recvfrom ( char* data, const int buffersize, struct sockaddr* from = NULL, socklen_t* fromlen = NULL) const; + + bool set_non_blocking ( const bool ); + + bool ReadLine (std::string& line); + + bool is_valid() const; + + private: + + SOCKET _sd; ///< Socket Descriptor + SOCKADDR_IN _sockaddr; ///< Socket Address + //struct addrinfo* _addrinfo; ///< Socket address info + std::string _hostname; ///< Hostname + unsigned short _port; ///< Port number + + enum SocketFamily _family; ///< Socket Address Family + enum SocketProtocol _protocol; ///< Socket Protocol + enum SocketType _type; ///< Socket Type + enum SocketDomain _domain; ///< Socket domain + + #ifdef TARGET_WINDOWS + WSADATA _wsaData; ///< Windows Socket data + static int win_usage_count; ///< Internal Windows usage counter used to prevent a global WSACleanup when more than one Socket object is used + #endif + + void errormessage( int errornum, const char* functionname = NULL) const; + int getLastError(void) const; + bool osInit(); + void osCleanup(); +}; + +} //namespace OCTO diff --git a/src/client.cpp b/src/client.cpp index d5f006b..783d515 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -27,6 +27,7 @@ #include #include "OctonetData.h" +#include "rtsp_client.hpp" using namespace ADDON; @@ -247,14 +248,28 @@ PVR_ERROR UpdateTimer(const PVR_TIMER& timer) { return PVR_ERROR_NOT_IMPLEMENTED /* PVR stream handling */ /* entirely unused, as we use standard RTSP+TS mux, which can be handlded by * Kodi core */ -bool OpenLiveStream(const PVR_CHANNEL& channel) { return false; } -void CloseLiveStream(void) {} -int ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize) { return -1; } +bool OpenLiveStream(const PVR_CHANNEL& channel) { + return rtsp_open(data->getUrl(channel.iUniqueId)); +} + +int ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize) { + return rtsp_read(pBuffer, iBufferSize); +} + +void CloseLiveStream(void) { + rtsp_close(); +} + long long SeekLiveStream(long long iPosition, int iWhence) { return -1; } long long PositionLiveStream(void) { return -1; } long long LengthLiveStream(void) { return -1; } bool IsRealTimeStream(void) { return true; } -bool SwitchChannel(const PVR_CHANNEL& channel) { return false; } + +bool SwitchChannel(const PVR_CHANNEL& channel) { + CloseLiveStream(); + return OpenLiveStream(channel); +} + PVR_ERROR SignalStatus(PVR_SIGNAL_STATUS& signalStatus) { return PVR_ERROR_NOT_IMPLEMENTED; } const char* GetLiveStreamURL(const PVR_CHANNEL& channel) { return NULL; } PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) { return PVR_ERROR_NOT_IMPLEMENTED; } diff --git a/src/rtsp_client.cpp b/src/rtsp_client.cpp new file mode 100644 index 0000000..a975233 --- /dev/null +++ b/src/rtsp_client.cpp @@ -0,0 +1,374 @@ +#include "rtsp_client.hpp" +#include +#include +#include +#include "Socket.h" +#include "client.h" +#include +#include +// #include +#include + +#define RTSP_DEFAULT_PORT 554 +#define RTSP_RECEIVE_BUFFER 2048 +#define RTP_HEADER_SIZE 12 +#define VLEN 100 +#define KEEPALIVE_INTERVAL 60 +#define KEEPALIVE_MARGIN 5 +#define UDP_ADDRESS_LEN 16 + +using namespace std; +using namespace ADDON; +using namespace OCTO; + +enum rtsp_state { + RTSP_IDLE, + RTSP_DESCRIBE, + RTSP_SETUP, + RTSP_PLAY, + RTSP_RUNNING +}; + +enum rtsp_result { + RTSP_RESULT_OK = 200, +}; + +struct rtsp_client { + char *content_base; + char *control; + char session_id[64]; + uint16_t stream_id; + int keepalive_interval; + + char udp_address[UDP_ADDRESS_LEN]; + uint16_t udp_port; + + Socket tcp_sock; + Socket udp_sock; + + enum rtsp_state state; + int cseq; + + size_t fifo_size; + uint16_t last_seq_nr; +}; + +struct url { + string protocol; + string host; + int port; + string path; +}; + +static rtsp_client *rtsp = NULL; + +static url parse_url(const std::string& str) { + static const string prot_end = "://"; + static const string host_end = "/"; + url result; + + string::const_iterator begin = str.begin(); + string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end()); + result.protocol.reserve(distance(begin, end)); + transform(begin, end, back_inserter(result.protocol), ::tolower); + advance(end, prot_end.size()); + begin = end; + + end = search(begin, str.end(), host_end.begin(), host_end.end()); + result.host.reserve(distance(begin, end)); + transform(begin, end, back_inserter(result.host), ::tolower); + advance(end, host_end.size()); + begin = end; + + result.port = RTSP_DEFAULT_PORT; + + result.path.reserve(distance(begin, str.end())); + transform(begin, str.end(), back_inserter(result.path), ::tolower); + + return result; +} + +static int tcp_sock_read_line(string &line) { + static string buf; + + while(true) { + string::size_type pos = buf.find("\r\n"); + if(pos != string::npos) { + line = buf.substr(0, pos); + buf.erase(0, pos + 2); + return 0; + } + + char tmp_buf[2048]; + int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1); + if(size <= 0) { + return 1; + } + + buf.append(&tmp_buf[0], &tmp_buf[size]); + } +} + +static string compose_url(const url& u) +{ + stringstream res; + res << u.protocol << "://" << u.host; + if (u.port > 0) + res << ":" << u.port; + res << "/" << u.path; + + return res.str(); +} + +static void parse_session(char *request_line, char *session, unsigned max, int *timeout) { + char *state; + char *tok; + + tok = strtok_r(request_line, ";", &state); + if (tok == NULL) + return; + strncpy(session, tok, min(strlen(tok), (size_t)(max - 1))); + + while ((tok = strtok_r(NULL, ";", &state)) != NULL) { + if (strncmp(tok, "timeout=", 8) == 0) { + *timeout = atoi(tok + 8); + if (*timeout > 5) + *timeout -= KEEPALIVE_MARGIN; + else if (*timeout > 0) + *timeout = 1; + } + } +} + +static int parse_port(char *str, uint16_t *port) +{ + int p = atoi(str); + if (p < 0 || p > UINT16_MAX) + return -1; + + *port = p; + + return 0; +} + +static int parse_transport(char *request_line) { + char *state; + char *tok; + int err; + + tok = strtok_r(request_line, ";", &state); + if (tok == NULL || strncmp(tok, "RTP/AVP", 7) != 0) + return -1; + + tok = strtok_r(NULL, ";", &state); + if (tok == NULL || strncmp(tok, "multicast", 9) != 0) + return 0; + + while ((tok = strtok_r(NULL, ";", &state)) != NULL) { + if (strncmp(tok, "destination=", 12) == 0) { + strncpy(rtsp->udp_address, tok + 12, min(strlen(tok + 12), (size_t)(UDP_ADDRESS_LEN - 1))); + } else if (strncmp(tok, "port=", 5) == 0) { + char port[6]; + char *end; + + memset(port, 0x00, 6); + strncpy(port, tok + 5, min(strlen(tok + 5), (size_t)5)); + if ((end = strstr(port, "-")) != NULL) + *end = '\0'; + err = parse_port(port, &rtsp->udp_port); + if (err) + return err; + } + } + + return 0; +} + +#define skip_whitespace(x) while(*x == ' ') x++ +static enum rtsp_result rtsp_handle() { + uint8_t buffer[512]; + int rtsp_result = 0; + bool have_header = false; + size_t content_length = 0; + size_t read = 0; + char *in, *val; + string in_str; + + /* Parse header */ + while (!have_header) { + if (tcp_sock_read_line(in_str) < 0) + break; + in = const_cast(in_str.c_str()); + + if (strncmp(in, "RTSP/1.0 ", 9) == 0) { + rtsp_result = atoi(in + 9); + } else if (strncmp(in, "Content-Base:", 13) == 0) { + free(rtsp->content_base); + + val = in + 13; + skip_whitespace(val); + + rtsp->content_base = strdup(val); + } else if (strncmp(in, "Content-Length:", 15) == 0) { + val = in + 16; + skip_whitespace(val); + + content_length = atoi(val); + } else if (strncmp("Session:", in, 8) == 0) { + val = in + 8; + skip_whitespace(val); + + parse_session(val, rtsp->session_id, 64, &rtsp->keepalive_interval); + } else if (strncmp("Transport:", in, 10) == 0) { + val = in + 10; + skip_whitespace(val); + + if (parse_transport(val) != 0) { + rtsp_result = -1; + break; + } + } else if (strncmp("com.ses.streamID:", in, 17) == 0) { + val = in + 17; + skip_whitespace(val); + + rtsp->stream_id = atoi(val); + } else if (in[0] == '\0') { + have_header = true; + } + } + + /* Discard further content */ + while (content_length > 0 && + (read = rtsp->tcp_sock.receive((char*)buffer, sizeof(buffer), min(sizeof(buffer), content_length)))) + content_length -= read; + + return (enum rtsp_result)rtsp_result; +} + +bool rtsp_open(const string& url_str) +{ + string setup_url_str; + const char *psz_setup_url; + stringstream setup_ss; + stringstream play_ss; + url setup_url; + + rtsp_close(); + rtsp = new rtsp_client(); + if (rtsp == NULL) + return false; + + kodi->Log(LOG_DEBUG, "try to open '%s'", url_str.c_str()); + + url dst = parse_url(url_str); + kodi->Log(LOG_DEBUG, "connect to host '%s'", dst.host.c_str()); + + if(!rtsp->tcp_sock.connect(dst.host, dst.port)) { + kodi->Log(LOG_ERROR, "Failed to connect to RTSP server %s:%d", dst.host.c_str(), dst.port); + goto error; + } + + // TODO: tcp keep alive? + + if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(), + dst.port) < 0) { + rtsp->content_base = NULL; + goto error; + } + + rtsp->last_seq_nr = 0; + rtsp->keepalive_interval = (KEEPALIVE_INTERVAL - KEEPALIVE_MARGIN); + + setup_url = dst; + + // reverse the satip protocol trick, as SAT>IP believes to be RTSP + if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5)) { + setup_url.protocol = "rtsp"; + } + + setup_url_str = compose_url(setup_url); + psz_setup_url = setup_url_str.c_str(); + + // TODO: Find available port + rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp); + rtsp->udp_port = 6785; + if(!rtsp->udp_sock.bind(rtsp->udp_port)) { + goto error; + } + + setup_ss << "SETUP " << setup_url_str<< " RTSP/1.0\r\n"; + setup_ss << "CSeq: " << rtsp->cseq++ << "\r\n"; + setup_ss << "Transport: RTP/AVP;unicast;client_port=" << rtsp->udp_port << "-" << (rtsp->udp_port + 1) << "\r\n\r\n"; + rtsp->tcp_sock.send(setup_ss.str()); + + if (rtsp_handle() != RTSP_RESULT_OK) { + kodi->Log(LOG_ERROR, "Failed to setup RTSP session"); + goto error; + } + + if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0) { + rtsp->control = NULL; + goto error; + } + + play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n"; + play_ss << "CSeq: " << rtsp->cseq++ << "\r\n"; + play_ss << "Session: " << rtsp->session_id << "\r\n\r\n"; + rtsp->tcp_sock.send(play_ss.str()); + + if (rtsp_handle() != RTSP_RESULT_OK) { + kodi->Log(LOG_ERROR, "Failed to play RTSP session"); + goto error; + } + + return true; + +error: + rtsp_close(); + return false; +} + +int rtsp_read(void *buf, unsigned buf_size) { + sockaddr addr; + socklen_t addr_len = sizeof(addr); + int ret = rtsp->udp_sock.recvfrom((char *)buf, buf_size, (sockaddr *)&addr, &addr_len); + + // TODO: check ip + + return ret; +} + +static void rtsp_teardown() { + if(!rtsp->tcp_sock.is_valid()) { + return; + } + + if (rtsp->session_id[0] > 0) { + char *msg; + int len; + stringstream ss; + + rtsp->udp_sock.close(); + + ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n"; + ss << "CSeq: " << rtsp->cseq++ << "\r\n"; + ss << "Session: " << rtsp->session_id << "\r\n\r\n"; + rtsp->tcp_sock.send(ss.str()); + + if (rtsp_handle() != RTSP_RESULT_OK) { + kodi->Log(LOG_ERROR, "Failed to teardown RTSP session"); + return; + } + } +} + +void rtsp_close() +{ + if(rtsp) { + rtsp_teardown(); + rtsp->tcp_sock.close(); + rtsp->udp_sock.close(); + delete rtsp; + rtsp = NULL; + } +} diff --git a/src/rtsp_client.hpp b/src/rtsp_client.hpp new file mode 100644 index 0000000..1186922 --- /dev/null +++ b/src/rtsp_client.hpp @@ -0,0 +1,10 @@ +#ifndef _RTSP_CLIENT_HPP_ +#define _RTSP_CLIENT_HPP_ + +#include + +bool rtsp_open(const std::string& url_str); +void rtsp_close(); +int rtsp_read(void *buf, unsigned buf_size); + +#endif