diff --git a/Makefile b/Makefile index 524338c..aba1c46 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ all-redirect: all ### The object files (add further files here): -OBJS = $(PLUGIN).o common.o config.o device.o discover.o param.o \ +OBJS = $(PLUGIN).o common.o config.o device.o discover.o param.o rtsp.o \ sectionfilter.o server.o setup.o socket.o statistics.o tuner.o ### The main target: diff --git a/rtsp.c b/rtsp.c new file mode 100644 index 0000000..d6d4558 --- /dev/null +++ b/rtsp.c @@ -0,0 +1,268 @@ +/* + * rtsp.c: SAT>IP plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include "common.h" +#include "rtsp.h" + +cSatipRtsp::cSatipRtsp(cSatipTunerIf &tunerP) +: tunerM(&tunerP), + tunerIdM(tunerM ? tunerM->GetId() : -1), + handleM(curl_easy_init()), + headerListM(NULL) +{ + if (handleM) { + CURLcode res = CURLE_OK; + +#ifdef DEBUG + // Verbose output + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_VERBOSE, 1L); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGFUNCTION, cSatipRtsp::DebugCallback); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGDATA, this); +#endif + + // No progress meter and no signaling + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOPROGRESS, 1L); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOSIGNAL, 1L); + + // Set timeouts + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_TIMEOUT_MS, (long)eConnectTimeoutMs); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_CONNECTTIMEOUT_MS, (long)eConnectTimeoutMs); + + // Set user-agent + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_USERAGENT, *cString::sprintf("vdr-%s/%s (device %d)", PLUGIN_NAME_I18N, VERSION, tunerIdM)); + } +} + +cSatipRtsp::~cSatipRtsp() +{ + if (handleM) { + // Cleanup curl stuff + if (headerListM) { + curl_slist_free_all(headerListM); + headerListM = NULL; + } + curl_easy_cleanup(handleM); + handleM = NULL; + } +} + +size_t cSatipRtsp::HeaderCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP) +{ + cSatipRtsp *obj = reinterpret_cast(dataP); + size_t len = sizeP * nmembP; + //debug("cSatipRtsp::%s(%zu)", __FUNCTION__, len); + + char *s, *p = (char *)ptrP; + char *r = strtok_r(p, "\r\n", &s); + + while (obj && obj->tunerM && r) { + //debug("cSatipRtsp::%s(%zu): %s", __FUNCTION__, len, r); + r = skipspace(r); + if (strstr(r, "com.ses.streamID")) { + int streamid = -1; + if (sscanf(r, "com.ses.streamID:%11d", &streamid) == 1) + obj->tunerM->SetStreamId(streamid); + } + else if (strstr(r, "Session:")) { + int timeout = -1; + char *session = NULL; + if (sscanf(r, "Session:%m[^;];timeout=%11d", &session, &timeout) == 2) + obj->tunerM->SetSessionTimeout(skipspace(session), timeout * 1000); + else if (sscanf(r, "Session:%m[^;]", &session) == 1) + obj->tunerM->SetSessionTimeout(skipspace(session), -1); + FREE_POINTER(session); + } + r = strtok_r(NULL, "\r\n", &s); + } + + return len; +} + +size_t cSatipRtsp::WriteCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP) +{ + cSatipRtsp *obj = reinterpret_cast(dataP); + size_t len = sizeP * nmembP; + //debug("cSatipRtsp::%s(%zu)", __FUNCTION__, len); + + if (obj && obj->tunerM && (len > 0)) { + char *data = strndup((char*)ptrP, len); + obj->tunerM->ParseReceptionParameters(data); + FREE_POINTER(data); + } + + return len; +} + +int cSatipRtsp::DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP) +{ + cSatipRtsp *obj = reinterpret_cast(userPtrP); + + if (obj && obj->tunerM) { + switch (typeP) { + case CURLINFO_TEXT: + debug("cSatipTuner::%s(%d): RTSP INFO %.*s", __FUNCTION__, obj->tunerM->GetId(), (int)sizeP, dataP); + break; + case CURLINFO_HEADER_IN: + debug("cSatipTuner::%s(%d): RTSP HEAD <<< %.*s", __FUNCTION__, obj->tunerM->GetId(), (int)sizeP, dataP); + break; + case CURLINFO_HEADER_OUT: + debug("cSatipTuner::%s(%d): RTSP HEAD >>>\n%.*s", __FUNCTION__, obj->tunerM->GetId(), (int)sizeP, dataP); + break; + case CURLINFO_DATA_IN: + debug("cSatipTuner::%s(%d): RTSP DATA <<< %.*s", __FUNCTION__, obj->tunerM->GetId(), (int)sizeP, dataP); + break; + case CURLINFO_DATA_OUT: + debug("cSatipTuner::%s(%d): RTSP DATA >>>\n%.*s", __FUNCTION__, obj->tunerM->GetId(), (int)sizeP, dataP); + break; + default: + break; + } + } + + return 0; +} + +cString cSatipRtsp::RtspUnescapeString(const char *strP) +{ + debug("cSatipRtsp::%s(%d): str=%s", __FUNCTION__, tunerIdM, strP); + if (handleM) { + char *p = curl_easy_unescape(handleM, strP, 0, NULL); + cString s = p; + curl_free(p); + + return s; + } + + return cString(strP); +} + +bool cSatipRtsp::Options(const char *uriP) +{ + debug("cSatipRtsp::%s(%d): uri=%s", __FUNCTION__, tunerIdM, uriP); + if (handleM && !isempty(uriP)) { + CURLcode res = CURLE_OK; + + if (!strstr(uriP, "?")) + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_URL, uriP); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS); + SATIP_CURL_EASY_PERFORM(handleM); + + return ValidateLatestResponse(); + } + + return false; +} + +bool cSatipRtsp::Setup(const char *uriP, int rtpPortP, int rtcpPortP) +{ + debug("cSatipRtsp::%s(%d): uri=%s rtp=%d rtcp=%d", __FUNCTION__, tunerIdM, uriP, rtpPortP, rtcpPortP); + if (handleM && !isempty(uriP)) { + CURLcode res = CURLE_OK; + cString transport = cString::sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPortP, rtcpPortP); + + // Setup media stream + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_TRANSPORT, *transport); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP); + // Set header callback for catching the session and timeout + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, cSatipRtsp::HeaderCallback); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, this); + SATIP_CURL_EASY_PERFORM(handleM); + // Session id is now known - disable header parsing + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, NULL); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, NULL); + + return ValidateLatestResponse(); + } + + return false; +} + +bool cSatipRtsp::SetSession(const char *sessionP) +{ + debug("cSatipRtsp::%s(%d): session=%s", __FUNCTION__, tunerIdM, sessionP); + if (handleM) { + CURLcode res = CURLE_OK; + + debug("cSatipRtsp::%s(%d): session id quirk enabled", __FUNCTION__, tunerIdM); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_SESSION_ID, sessionP); + } + + return true; +} + +bool cSatipRtsp::Describe(const char *uriP) +{ + debug("cSatipRtsp::%s(%d): uri=%s", __FUNCTION__, tunerIdM, uriP); + if (handleM && !isempty(uriP)) { + CURLcode res = CURLE_OK; + + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_DESCRIBE); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, cSatipRtsp::WriteCallback); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, this); + SATIP_CURL_EASY_PERFORM(handleM); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, NULL); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, NULL); + + return ValidateLatestResponse(); + } + + return false; +} + +bool cSatipRtsp::Play(const char *uriP) +{ + debug("cSatipRtsp::%s(%d): uri=%s", __FUNCTION__, tunerIdM, uriP); + if (handleM && !isempty(uriP)) { + CURLcode res = CURLE_OK; + + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_PLAY); + SATIP_CURL_EASY_PERFORM(handleM); + + return ValidateLatestResponse(); + } + + return false; +} + +bool cSatipRtsp::Teardown(const char *uriP) +{ + debug("cSatipRtsp::%s(%d): uri=%s", __FUNCTION__, tunerIdM, uriP); + if (handleM && !isempty(uriP)) { + CURLcode res = CURLE_OK; + + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_TEARDOWN); + SATIP_CURL_EASY_PERFORM(handleM); + + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_CLIENT_CSEQ, 1L); + SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_SESSION_ID, NULL); + + return ValidateLatestResponse(); + } + + return false; +} + +bool cSatipRtsp::ValidateLatestResponse(void) +{ + debug("cSatipRtsp::%s(%d)", __FUNCTION__, tunerIdM); + if (handleM) { + long rc = 0; + CURLcode res = CURLE_OK; + SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_RESPONSE_CODE, &rc); + if (rc == 200) + return true; + else if (rc != 0) + error("Tuner detected invalid status code %ld [device %d]", rc, tunerIdM); + } + + return false; +} diff --git a/rtsp.h b/rtsp.h new file mode 100644 index 0000000..75b77e7 --- /dev/null +++ b/rtsp.h @@ -0,0 +1,54 @@ +/* + * rtsp.h: SAT>IP plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __SATIP_RTSP_H +#define __SATIP_RTSP_H + +#include +#include + +#ifndef CURLOPT_RTSPHEADER +#error "libcurl is missing required RTSP support" +#endif + +#include "tunerif.h" + +class cSatipRtsp { +private: + static size_t HeaderCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP); + static size_t WriteCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP); + static int DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP); + + enum { + eConnectTimeoutMs = 1500, // in milliseconds + }; + + cSatipTunerIf* tunerM; + int tunerIdM; + CURL *handleM; + struct curl_slist *headerListM; + + bool ValidateLatestResponse(void); + + // to prevent copy constructor and assignment + cSatipRtsp(const cSatipRtsp&); + cSatipRtsp& operator=(const cSatipRtsp&); + +public: + cSatipRtsp(cSatipTunerIf &tunerP); + virtual ~cSatipRtsp(); + + cString RtspUnescapeString(const char *strP); + bool Options(const char *uriP); + bool Setup(const char *uriP, int rtpPortP, int rtcpPortP); + bool SetSession(const char *sessionP); + bool Describe(const char *uriP); + bool Play(const char *uriP); + bool Teardown(const char *uriP); +}; + +#endif // __SATIP_RTSP_H diff --git a/socket.c b/socket.c index 1581577..3ec2979 100644 --- a/socket.c +++ b/socket.c @@ -38,7 +38,6 @@ cSatipSocket::~cSatipSocket() bool cSatipSocket::Open(const int portP) { - debug("cSatipSocket::%s(%d)", __FUNCTION__, portP); // Bind to the socket if it is not active already if (socketDescM < 0) { socklen_t len = sizeof(sockAddrM); @@ -64,12 +63,13 @@ bool cSatipSocket::Open(const int portP) "getsockname()", Close(), return false); socketPortM = ntohs(sockAddrM.sin_port); } + debug("cSatipSocket::%s(%d): socketPort=%d", __FUNCTION__, portP, socketPortM); return true; } void cSatipSocket::Close(void) { - debug("cSatipSocket::%s()", __FUNCTION__); + debug("cSatipSocket::%s(%d)", __FUNCTION__, socketPortM); // Check if socket exists if (socketDescM >= 0) { close(socketDescM); diff --git a/tuner.c b/tuner.c index b25308a..3619d17 100644 --- a/tuner.c +++ b/tuner.c @@ -14,7 +14,9 @@ cSatipTuner::cSatipTuner(cSatipDeviceIf &deviceP, unsigned int packetLenP) : cThread("SAT>IP tuner"), sleepM(), deviceM(&deviceP), + deviceIdM(deviceM ? deviceM->GetId() : -1), packetBufferLenM(packetLenP), + rtspM(new cSatipRtsp(*this)), rtpSocketM(new cSatipSocket()), rtcpSocketM(new cSatipSocket()), streamAddrM(""), @@ -22,8 +24,6 @@ cSatipTuner::cSatipTuner(cSatipDeviceIf &deviceP, unsigned int packetLenP) currentServerM(NULL), nextServerM(NULL), mutexM(), - handleM(NULL), - headerListM(NULL), keepAliveM(), statusUpdateM(), pidUpdateCacheM(), @@ -39,110 +39,52 @@ cSatipTuner::cSatipTuner(cSatipDeviceIf &deviceP, unsigned int packetLenP) delPidsM(), pidsM() { - debug("cSatipTuner::%s(%d) [device %d]", __FUNCTION__, packetBufferLenM, deviceM->GetId()); + debug("cSatipTuner::%s(%d) [device %d]", __FUNCTION__, packetBufferLenM, deviceIdM); // Allocate packet buffer packetBufferM = MALLOC(unsigned char, packetBufferLenM); if (packetBufferM) memset(packetBufferM, 0, packetBufferLenM); else - error("MALLOC() failed for packet buffer [device %d]", deviceM->GetId()); + error("MALLOC() failed for packet buffer [device %d]", deviceIdM); + + // Open sockets + int i = 100; + while (i-- > 0) { + if (rtpSocketM->Open() && rtcpSocketM->Open(rtpSocketM->Port() + 1)) + break; + rtpSocketM->Close(); + rtcpSocketM->Close(); + } + if ((rtpSocketM->Port() <= 0) || (rtcpSocketM->Port() <= 0)) { + error("Cannot open required RTP/RTCP ports [device %d]", deviceIdM); + } + // Start thread Start(); } cSatipTuner::~cSatipTuner() { - debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); // Stop thread sleepM.Signal(); if (Running()) Cancel(3); Close(); + + // Close the listening sockets + rtpSocketM->Close(); + rtcpSocketM->Close(); + // Free allocated memory free(packetBufferM); DELETENULL(rtcpSocketM); DELETENULL(rtpSocketM); } -size_t cSatipTuner::HeaderCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP) -{ - cSatipTuner *obj = reinterpret_cast(dataP); - size_t len = sizeP * nmembP; - //debug("cSatipTuner::%s(%zu)", __FUNCTION__, len); - - char *s, *p = (char *)ptrP; - char *r = strtok_r(p, "\r\n", &s); - - while (obj && r) { - //debug("cSatipTuner::%s(%zu): %s", __FUNCTION__, len, r); - r = skipspace(r); - if (strstr(r, "com.ses.streamID")) { - int streamid = -1; - if (sscanf(r, "com.ses.streamID:%11d", &streamid) == 1) - obj->SetStreamId(streamid); - } - else if (strstr(r, "Session:")) { - int timeout = -1; - char *session = NULL; - if (sscanf(r, "Session:%m[^;];timeout=%11d", &session, &timeout) == 2) - obj->SetSessionTimeout(skipspace(session), timeout * 1000); - else if (sscanf(r, "Session:%m[^;]", &session) == 1) - obj->SetSessionTimeout(skipspace(session)); - FREE_POINTER(session); - } - r = strtok_r(NULL, "\r\n", &s); - } - - return len; -} - -size_t cSatipTuner::DataCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP) -{ - cSatipTuner *obj = reinterpret_cast(dataP); - size_t len = sizeP * nmembP; - //debug("cSatipTuner::%s(%zu)", __FUNCTION__, len); - - if (obj && (len > 0)) { - char *data = strndup((char*)ptrP, len); - obj->ParseReceptionParameters(data); - FREE_POINTER(data); - } - - return len; -} - -int cSatipTuner::DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP) -{ - cSatipTuner *obj = reinterpret_cast(userPtrP); - - if (obj) { - switch (typeP) { - case CURLINFO_TEXT: - debug("cSatipTuner::%s(%d): RTSP INFO %.*s", __FUNCTION__, obj->deviceM->GetId(), (int)sizeP, dataP); - break; - case CURLINFO_HEADER_IN: - debug("cSatipTuner::%s(%d): RTSP HEAD <<< %.*s", __FUNCTION__, obj->deviceM->GetId(), (int)sizeP, dataP); - break; - case CURLINFO_HEADER_OUT: - debug("cSatipTuner::%s(%d): RTSP HEAD >>> %.*s", __FUNCTION__, obj->deviceM->GetId(), (int)sizeP, dataP); - break; - case CURLINFO_DATA_IN: - debug("cSatipTuner::%s(%d): RTSP DATA <<< %.*s", __FUNCTION__, obj->deviceM->GetId(), (int)sizeP, dataP); - break; - case CURLINFO_DATA_OUT: - debug("cSatipTuner::%s(%d): RTSP DATA >>> %.*s", __FUNCTION__, obj->deviceM->GetId(), (int)sizeP, dataP); - break; - default: - break; - } - } - - return 0; -} - void cSatipTuner::Action(void) { - debug("cSatipTuner::%s(): entering [device %d]", __FUNCTION__, deviceM->GetId()); + debug("cSatipTuner::%s(): entering [device %d]", __FUNCTION__, deviceIdM); cTimeMs timeout(eReConnectTimeoutMs); // Increase priority SetPriority(-1); @@ -190,134 +132,54 @@ void cSatipTuner::Action(void) sleepM.Wait(10); // to avoid busy loop and reduce cpu load } } - debug("cSatipTuner::%s(): exiting [device %d]", __FUNCTION__, deviceM->GetId()); + debug("cSatipTuner::%s(): exiting [device %d]", __FUNCTION__, deviceIdM); } bool cSatipTuner::Open(void) { - debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); return Connect(); } bool cSatipTuner::Close(void) { - debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); return Disconnect(); } bool cSatipTuner::Connect(void) { cMutexLock MutexLock(&mutexM); - debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); - - // Initialize the curl session - if (!handleM) - handleM = curl_easy_init(); - - if (handleM && !isempty(*streamAddrM)) { - cString uri, control, transport, range; - CURLcode res = CURLE_OK; + debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); + if (!isempty(*streamAddrM)) { + cString connectionUri = cString::sprintf("rtsp://%s", *streamAddrM); + cString uri = cString::sprintf("%s/?%s", *connectionUri, *streamParamM); // Just retune if (tunedM && (streamIdM >= 0)) { - debug("cSatipTuner::%s(): retune [device %d]", __FUNCTION__, deviceM->GetId()); + debug("cSatipTuner::%s(): retune [device %d]", __FUNCTION__, deviceIdM); keepAliveM.Set(0); KeepAlive(); // Flush any old content if (rtpSocketM) rtpSocketM->Flush(); - - uri = cString::sprintf("rtsp://%s/?%s", *streamAddrM, *streamParamM); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, *uri); - transport = cString::sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpSocketM->Port(), rtcpSocketM->Port()); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_TRANSPORT, *transport); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP); - SATIP_CURL_EASY_PERFORM(handleM); - - openedM = true; + openedM = rtspM->Setup(*uri, rtpSocketM->Port(), rtcpSocketM->Port()); return openedM; } - -#ifdef DEBUG - // Verbose output - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_VERBOSE, 1L); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGFUNCTION, cSatipTuner::DebugCallback); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGDATA, this); -#endif - - // No progress meter and no signaling - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOPROGRESS, 1L); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOSIGNAL, 1L); - - // Set timeouts - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_TIMEOUT_MS, (long)eConnectTimeoutMs); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_CONNECTTIMEOUT_MS, (long)eConnectTimeoutMs); - - // Set user-agent - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_USERAGENT, *cString::sprintf("vdr-%s/%s (device %d)", PLUGIN_NAME_I18N, VERSION, deviceM->GetId())); - - // Set URL - char *p = curl_easy_unescape(handleM, *streamAddrM, 0, NULL); - streamAddrM = p; - curl_free(p); - uri = cString::sprintf("rtsp://%s/", *streamAddrM); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_URL, *uri); - - // Open sockets - int i = 100; - while (i-- > 0) { - if (rtpSocketM->Open() && rtcpSocketM->Open(rtpSocketM->Port() + 1)) - break; - rtpSocketM->Close(); - rtcpSocketM->Close(); - } - if ((rtpSocketM->Port() <= 0) || (rtcpSocketM->Port() <= 0)) { - error("Cannot open required RTP/RTCP ports [device %d]", deviceM->GetId()); - openedM = false; - return openedM; - } - - // Request server options keepAliveM.Set(timeoutM); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, *uri); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS); - SATIP_CURL_EASY_PERFORM(handleM); - if (!ValidateLatestResponse()) { - openedM = false; - return openedM; + openedM = rtspM->Options(*connectionUri) && rtspM->Setup(*uri, rtpSocketM->Port(), rtcpSocketM->Port()); + if (openedM) { + if (nextServerM && nextServerM->Quirk(cSatipServer::eSatipQuirkSessionId)) + rtspM->SetSession(SkipZeroes(*sessionM)); + tunedM = true; + UpdatePids(true); + if (nextServerM) { + cSatipDiscover::GetInstance()->UseServer(nextServerM, true); + currentServerM = nextServerM; + nextServerM = NULL; + } } - // Setup media stream: "&pids=all" for the whole mux - uri = cString::sprintf("rtsp://%s/?%s", *streamAddrM, *streamParamM); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, *uri); - transport = cString::sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpSocketM->Port(), rtcpSocketM->Port()); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_TRANSPORT, *transport); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP); - // Set header callback for catching the session and timeout - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, cSatipTuner::HeaderCallback); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, this); - SATIP_CURL_EASY_PERFORM(handleM); - // Session id is now known - disable header parsing - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, NULL); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, NULL); - if (nextServerM && nextServerM->Quirk(cSatipServer::eSatipQuirkSessionId) && !isempty(*sessionM) && startswith(*sessionM, "0")) { - debug("cSatipTuner::%s(): session id quirk [device %d]", __FUNCTION__, deviceM->GetId()); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_SESSION_ID, SkipZeroes(*sessionM)); - } - if (!ValidateLatestResponse()) { - openedM = false; - return openedM; - } - - // Start playing - tunedM = true; - UpdatePids(true); - if (nextServerM) { - cSatipDiscover::GetInstance()->UseServer(nextServerM, true); - currentServerM = nextServerM; - nextServerM = NULL; - } - openedM = true; return openedM; } @@ -328,35 +190,14 @@ bool cSatipTuner::Connect(void) bool cSatipTuner::Disconnect(void) { cMutexLock MutexLock(&mutexM); - debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); openedM = false; - // Terminate curl session - if (handleM) { - // Teardown rtsp session - if (!isempty(*streamAddrM) && streamIdM >= 0) { - CURLcode res = CURLE_OK; - - cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, *uri); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_TEARDOWN); - SATIP_CURL_EASY_PERFORM(handleM); - ValidateLatestResponse(); - } - - // Cleanup curl stuff - if (headerListM) { - curl_slist_free_all(headerListM); - headerListM = NULL; - } - curl_easy_cleanup(handleM); - handleM = NULL; + if (!isempty(*streamAddrM)) { + cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM); + rtspM->Teardown(*uri); } - // Close the listening sockets - rtpSocketM->Close(); - rtcpSocketM->Close(); - // Reset signal parameters hasLockM = false; signalStrengthM = -1; @@ -374,25 +215,9 @@ bool cSatipTuner::Disconnect(void) return true; } -bool cSatipTuner::ValidateLatestResponse(void) -{ - //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); - if (handleM) { - long rc = 0; - CURLcode res = CURLE_OK; - SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_RESPONSE_CODE, &rc); - if (rc == 200) - return true; - else if (rc != 0) - error("Tuner detected invalid status code %ld [device %d]", rc, deviceM->GetId()); - } - - return false; -} - void cSatipTuner::ParseReceptionParameters(const char *paramP) { - //debug("cSatipTuner::%s(%s) [device %d]", __FUNCTION__, paramP, deviceM->GetId()); + //debug("cSatipTuner::%s(%s) [device %d]", __FUNCTION__, paramP, deviceIdM); // DVB-S2: // ver=.;src=;tuner=,,,,,,,,,,,;pids=,..., // DVB-T2: @@ -439,27 +264,33 @@ void cSatipTuner::ParseReceptionParameters(const char *paramP) void cSatipTuner::SetStreamId(int streamIdP) { cMutexLock MutexLock(&mutexM); - debug("cSatipTuner::%s(%d) [device %d]", __FUNCTION__, streamIdP, deviceM->GetId()); + debug("cSatipTuner::%s(%d) [device %d]", __FUNCTION__, streamIdP, deviceIdM); streamIdM = streamIdP; } void cSatipTuner::SetSessionTimeout(const char *sessionP, int timeoutP) { cMutexLock MutexLock(&mutexM); - debug("cSatipTuner::%s(%s, %d) [device %d]", __FUNCTION__, sessionP, timeoutP, deviceM->GetId()); + debug("cSatipTuner::%s(%s, %d) [device %d]", __FUNCTION__, sessionP, timeoutP, deviceIdM); sessionM = sessionP; timeoutM = (timeoutP > eMinKeepAliveIntervalMs) ? timeoutP : eMinKeepAliveIntervalMs; } +int cSatipTuner::GetId(void) +{ + //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); + return deviceIdM; +} + bool cSatipTuner::SetSource(cSatipServer *serverP, const char *parameterP, const int indexP) { - debug("cSatipTuner::%s(%s, %d) [device %d]", __FUNCTION__, parameterP, indexP, deviceM->GetId()); + debug("cSatipTuner::%s(%s, %d) [device %d]", __FUNCTION__, parameterP, indexP, deviceIdM); cMutexLock MutexLock(&mutexM); if (serverP) { nextServerM = cSatipDiscover::GetInstance()->GetServer(serverP); if (nextServerM && !isempty(nextServerM->Address()) && !isempty(parameterP)) { // Update stream address and parameter - streamAddrM = nextServerM->Address(); + streamAddrM = rtspM->RtspUnescapeString(nextServerM->Address()); streamParamM = parameterP; // Reconnect Connect(); @@ -472,7 +303,7 @@ bool cSatipTuner::SetSource(cSatipServer *serverP, const char *parameterP, const bool cSatipTuner::SetPid(int pidP, int typeP, bool onP) { - //debug("cSatipTuner::%s(%d, %d, %d) [device %d]", __FUNCTION__, pidP, typeP, onP, deviceM->GetId()); + //debug("cSatipTuner::%s(%d, %d, %d) [device %d]", __FUNCTION__, pidP, typeP, onP, deviceIdM); cMutexLock MutexLock(&mutexM); if (onP) { pidsM.AddPid(pidP); @@ -492,8 +323,7 @@ bool cSatipTuner::UpdatePids(bool forceP) { cMutexLock MutexLock(&mutexM); if (((forceP && pidsM.Size()) || (pidUpdateCacheM.TimedOut() && (addPidsM.Size() || delPidsM.Size()))) && - tunedM && handleM && !isempty(*streamAddrM) && (streamIdM > 0)) { - CURLcode res = CURLE_OK; + tunedM && !isempty(*streamAddrM) && (streamIdM > 0)) { cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM); bool usedummy = !!(currentServerM && currentServerM->Quirk(cSatipServer::eSatipQuirkPlayPids)); if (forceP || usedummy) { @@ -517,18 +347,12 @@ bool cSatipTuner::UpdatePids(bool forceP) uri = cString::sprintf("%s%d%s", *uri, delPidsM[i], (i == (delPidsM.Size() - 1)) ? "" : ","); } } - //debug("cSatipTuner::%s(): %s [device %d]", __FUNCTION__, *uri, deviceM->GetId()); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, *uri); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_PLAY); - SATIP_CURL_EASY_PERFORM(handleM); - if (ValidateLatestResponse()) { + if (rtspM->Play(*uri)) { addPidsM.Clear(); delPidsM.Clear(); + return true; } - else - Disconnect(); - - return true; + Disconnect(); } return false; @@ -537,20 +361,13 @@ bool cSatipTuner::UpdatePids(bool forceP) bool cSatipTuner::KeepAlive(void) { cMutexLock MutexLock(&mutexM); - if (tunedM && handleM && keepAliveM.TimedOut()) { - debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); - CURLcode res = CURLE_OK; + if (tunedM && keepAliveM.TimedOut()) { cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM); - - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, *uri); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS); - SATIP_CURL_EASY_PERFORM(handleM); - if (ValidateLatestResponse()) + if (rtspM->Options(*uri)) { keepAliveM.Set(timeoutM); - else - Disconnect(); - - return true; + return true; + } + Disconnect(); } return false; @@ -559,24 +376,13 @@ bool cSatipTuner::KeepAlive(void) bool cSatipTuner::ReadReceptionStatus(void) { cMutexLock MutexLock(&mutexM); - if (tunedM && handleM && !pidsM.Size() && statusUpdateM.TimedOut() ) { - debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); - CURLcode res = CURLE_OK; + if (tunedM && !pidsM.Size() && statusUpdateM.TimedOut() ) { cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM); - - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, *uri); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_DESCRIBE); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, cSatipTuner::DataCallback); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, this); - SATIP_CURL_EASY_PERFORM(handleM); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, NULL); - SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, NULL); - if (ValidateLatestResponse()) + if (rtspM->Describe(*uri)) { statusUpdateM.Set(eStatusUpdateTimeoutMs); - else - Disconnect(); - - return true; + return true; + } + Disconnect(); } return false; @@ -584,30 +390,30 @@ bool cSatipTuner::ReadReceptionStatus(void) int cSatipTuner::SignalStrength(void) { - //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); return signalStrengthM; } int cSatipTuner::SignalQuality(void) { - //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); return signalQualityM; } bool cSatipTuner::HasLock(void) { - //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); return tunedM && hasLockM; } cString cSatipTuner::GetSignalStatus(void) { - //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); return cString::sprintf("lock=%d strength=%d quality=%d", HasLock(), SignalStrength(), SignalQuality()); } cString cSatipTuner::GetInformation(void) { - //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceM->GetId()); + //debug("cSatipTuner::%s() [device %d]", __FUNCTION__, deviceIdM); return tunedM ? cString::sprintf("rtsp://%s/?%s [stream=%d]", *streamAddrM, *streamParamM, streamIdM) : "connection failed"; } diff --git a/tuner.h b/tuner.h index 5c36b82..b08ce16 100644 --- a/tuner.h +++ b/tuner.h @@ -8,17 +8,11 @@ #ifndef __SATIP_TUNER_H #define __SATIP_TUNER_H -#include -#include - -#ifndef CURLOPT_RTSPHEADER -#error "libcurl is missing required RTSP support" -#endif - #include #include #include "deviceif.h" +#include "rtsp.h" #include "server.h" #include "statistics.h" #include "socket.h" @@ -49,7 +43,7 @@ public: } }; -class cSatipTuner : public cThread, public cSatipTunerStatistics { +class cSatipTuner : public cThread, public cSatipTunerStatistics, public cSatipTunerIf { private: enum { eDummyPid = 100, @@ -62,14 +56,12 @@ private: eMinKeepAliveIntervalMs = 30000 // in milliseconds }; - static size_t HeaderCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP); - static size_t DataCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP); - static int DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP); - cCondWait sleepM; cSatipDeviceIf* deviceM; + int deviceIdM; unsigned char* packetBufferM; unsigned int packetBufferLenM; + cSatipRtsp *rtspM; cSatipSocket *rtpSocketM; cSatipSocket *rtcpSocketM; cString streamAddrM; @@ -77,8 +69,6 @@ private: cSatipServer *currentServerM; cSatipServer *nextServerM; cMutex mutexM; - CURL *handleM; - struct curl_slist *headerListM; cTimeMs keepAliveM; cTimeMs statusUpdateM; cTimeMs signalInfoCacheM; @@ -97,10 +87,6 @@ private: bool Connect(void); bool Disconnect(void); - bool ValidateLatestResponse(void); - void ParseReceptionParameters(const char *paramP); - void SetStreamId(int streamIdP); - void SetSessionTimeout(const char *sessionP, int timeoutP = 0); bool KeepAlive(void); bool ReadReceptionStatus(void); bool UpdateSignalInfoCache(void); @@ -122,6 +108,13 @@ public: bool HasLock(void); cString GetSignalStatus(void); cString GetInformation(void); + + // for internal tuner interface +public: + virtual void ParseReceptionParameters(const char *paramP); + virtual void SetStreamId(int streamIdP); + virtual void SetSessionTimeout(const char *sessionP, int timeoutP); + virtual int GetId(void); }; #endif // __SATIP_TUNER_H diff --git a/tunerif.h b/tunerif.h new file mode 100644 index 0000000..6d2585a --- /dev/null +++ b/tunerif.h @@ -0,0 +1,25 @@ +/* + * tunerif.h: SAT>IP plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __SATIP_TUNERIF_H +#define __SATIP_TUNERIF_H + +class cSatipTunerIf { +public: + cSatipTunerIf() {} + virtual ~cSatipTunerIf() {} + virtual void ParseReceptionParameters(const char *paramP) = 0; + virtual void SetStreamId(int streamIdP) = 0; + virtual void SetSessionTimeout(const char *sessionP, int timeoutP) = 0; + virtual int GetId(void) = 0; + +private: + cSatipTunerIf(const cSatipTunerIf&); + cSatipTunerIf& operator=(const cSatipTunerIf&); +}; + +#endif // __SATIP_TUNERIF_H