mirror of
https://github.com/DigitalDevices/pvr.octonet.git
synced 2023-10-10 13:36:57 +02:00
Reimplementation with custom rtsp client
This commit is contained in:
parent
88fd3079aa
commit
5f5d2965cf
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
722
src/Socket.cpp
Normal file
722
src/Socket.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include "libXBMC_addon.h"
|
||||
#include <string>
|
||||
#include "p8-platform/os.h"
|
||||
#include "client.h"
|
||||
#include "Socket.h"
|
||||
#include <cstdio>
|
||||
|
||||
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<sockaddr*>( (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
|
305
src/Socket.h
Normal file
305
src/Socket.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#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 <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#pragma warning(default:4005)
|
||||
#include <windows.h>
|
||||
|
||||
#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 <sys/types.h> /* for socket,connect */
|
||||
#include <sys/socket.h> /* for socket,connect */
|
||||
#include <sys/un.h> /* for Unix socket */
|
||||
#include <arpa/inet.h> /* for inet_pton */
|
||||
#include <netdb.h> /* for gethostbyname */
|
||||
#include <netinet/in.h> /* for htons */
|
||||
#include <unistd.h> /* for read, write, close */
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
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 <vector>
|
||||
|
||||
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
|
@ -27,6 +27,7 @@
|
||||
#include <kodi/libKODI_guilib.h>
|
||||
|
||||
#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; }
|
||||
|
374
src/rtsp_client.cpp
Normal file
374
src/rtsp_client.cpp
Normal file
@ -0,0 +1,374 @@
|
||||
#include "rtsp_client.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <iterator>
|
||||
#include "Socket.h"
|
||||
#include "client.h"
|
||||
#include <p8-platform/util/util.h>
|
||||
#include <kodi/libXBMC_addon.h>
|
||||
// #include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#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<char *>(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;
|
||||
}
|
||||
}
|
10
src/rtsp_client.hpp
Normal file
10
src/rtsp_client.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef _RTSP_CLIENT_HPP_
|
||||
#define _RTSP_CLIENT_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
bool rtsp_open(const std::string& url_str);
|
||||
void rtsp_close();
|
||||
int rtsp_read(void *buf, unsigned buf_size);
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user