[vdr-plugin-satip] 02/06: New upstream version 2.2.4

Tobias Grimm tiber-guest at moszumanska.debian.org
Sat Jan 7 11:37:58 UTC 2017


This is an automated email from the git hooks/post-receive script.

tiber-guest pushed a commit to branch master
in repository vdr-plugin-satip.

commit 8c82d5dc8b01d3b66e68c50ca3dfef32a1da6b00
Author: Tobias Grimm <etobi at debian.org>
Date:   Tue Jan 3 19:57:48 2017 +0100

    New upstream version 2.2.4
---
 HISTORY         |  12 ++
 README          |  20 +++-
 common.h        |  22 ++--
 config.c        |   3 +
 config.h        |  18 +++
 device.c        |  22 +++-
 device.h        |   1 +
 deviceif.h      |   1 +
 discover.c      |  77 +++++++++++--
 discover.h      |  19 +++-
 msearch.c       |   7 +-
 msearch.h       |   1 +
 param.c         | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 param.h         |   1 +
 po/ca_ES.po     |  26 ++++-
 po/de_DE.po     |  39 +++++--
 po/es_ES.po     |  26 ++++-
 po/fi_FI.po     |  31 ++++-
 poller.c        |   2 +-
 pollerif.h      |   1 +
 rtcp.c          |  10 ++
 rtcp.h          |   1 +
 rtp.c           |  16 +++
 rtp.h           |   1 +
 rtsp.c          | 105 +++++++++++++++--
 rtsp.h          |  12 +-
 satip.c         |  80 +++++++++++--
 sectionfilter.c |  13 +++
 sectionfilter.h |   1 +
 server.c        | 196 ++++++++++++++++++++++++--------
 server.h        |  30 ++++-
 setup.c         |  28 ++++-
 setup.h         |   2 +
 socket.c        | 155 ++++++++++++++++++++++++-
 socket.h        |  11 +-
 tuner.c         | 117 +++++++++++++++----
 tuner.h         |   7 ++
 tunerif.h       |   3 +
 38 files changed, 1277 insertions(+), 184 deletions(-)

diff --git a/HISTORY b/HISTORY
index c2af5dd..0166834 100644
--- a/HISTORY
+++ b/HISTORY
@@ -144,3 +144,15 @@ VDR Plugin 'satip' Revision History
 - Reset the RTSP connection after any failed connect.
 - Added tweaks for minisatip and Schwaiger MS41IP.
 - Updated for vdr-2.3.1 (Thanks to Klaus Schmidinger).
+
+2016-12-18: Version 2.2.4
+
+- Updated German translation (Thanks to Frank Neumann).
+- Fixed Panasonic CXW804 support (Thanks to Tobias Grimm).
+- Fixed C++11 support (Thanks to Tobias Grimm).
+- Fixed server assigment with source validation (Thanks to Patrick Boettcher).
+- Added configurable RTP/RTCP ports (Thanks to chriszero).
+- Added support for X-SATIP-RTSP-Port header.
+- Added multicast and RTP-over-TCP support.
+- Added support for activating/deactivating server on-the-fly.
+- Extended command-line parameters for setting server quirks.
diff --git a/README b/README
index 49b0b38..0d414ab 100644
--- a/README
+++ b/README
@@ -52,9 +52,15 @@ separated list of "<ipaddress>|<model>|<description>" entries. The model
 consists of a DVB system (DVBS2,DVBT2,DVBT,DVBC) and number of available
 frontends separated by a hyphen:
 
-vdr -P 'satip -s <ipaddress>|<model>|<description>;...'
-vdr -P 'satip -s 192.168.0.1|DVBS2-2,DVBT2-2|Octo1'
-vdr -P 'satip -s 192.168.0.1|DVBS2-4|Octo1;192.168.0.2|DVBT2-4|Octo2'
+vdr -P 'satip -s <ipaddress>[:<port>]|<model>[:<filter>]|<description>[:<quirk>];...'
+vdr -P 'satip -s 192.168.0.1|DVBS2-2,DVBT2-2|OctopusNet'
+vdr -P 'satip -s 192.168.0.1|DVBS2-4|OctopusNet;192.168.0.2|DVBT2-4|minisatip:0x18'
+vdr -P 'satip -s 192.168.0.1:554|DVBS2-2:S19.2E|OctopusNet;192.168.0.2:8554|DVBS2-4:S19.2E,S1W|minisatip'
+
+The plugin accepts a "--portrange" (-p) command-line parameter, that can
+be used to manually specify the RTP & RTCP port range and therefore
+enables using the plugin through a NAT (e.g. Docker bridged network).
+A minimum of 2 ports per device is required.
 
 SAT>IP satellite positions (aka. signal sources) shall be defined via
 sources.conf. If the source description begins with a number, it's used
@@ -110,6 +116,11 @@ Setup menu:
                              "Disable filter" options which allow you
                              to disable the individual section filters.
                              Valid range: "none" = 0 ... 7
+- Transport mode = unicast   If you want to use the non-standard
+                   multicast RTP-over-TCP transport mode, set this option
+                   rtp-o-tcp accordingly. Otherwise, the transport
+                             mode will be RTP-over-UDP via unicast or
+                             multicast.
 - [Red:Scan]                 Forces network scanning of SAT>IP hardware.
 - [Yellow:Devices]           Opens SAT>IP device status menu.
 - [Blue:Info]                Opens SAT>IP information/statistics menu.
@@ -125,6 +136,9 @@ Information menu:
 
 Notes:
 
+- If you are having problems receiving DVB-S2 channels, make sure your
+  channels.conf entry contains correct pilot tone setting.
+
 - The stream id "-1" states about unsuccessful tuning. This might be a
   result of invalid channel parameters or lack of free SAT>IP tuners.
 
diff --git a/common.h b/common.h
index ac9ea20..d5539a8 100644
--- a/common.h
+++ b/common.h
@@ -13,9 +13,11 @@
 #include <vdr/config.h>
 #include <vdr/i18n.h>
 
+#define SATIP_DEFAULT_RTSP_PORT          554
+
 #define SATIP_MAX_DEVICES                MAXDEVICES
 
-#define SATIP_BUFFER_SIZE                KILOBYTE(1024)
+#define SATIP_BUFFER_SIZE                KILOBYTE(2048)
 
 #define SATIP_DEVICE_INFO_ALL            0
 #define SATIP_DEVICE_INFO_GENERAL        1
@@ -48,15 +50,15 @@
      esyslog("curl_easy_perform() [%s,%d] failed: %s (%d)",  __FILE__, __LINE__, curl_easy_strerror(res), res); \
      }
 
-#define ERROR_IF_FUNC(exp, errstr, func, ret)                \
-  do {                                                       \
-     if (exp) {                                              \
-        char tmp[64];                                        \
-        esyslog("[%s,%d]: "errstr": %s", __FILE__, __LINE__, \
-                strerror_r(errno, tmp, sizeof(tmp)));        \
-        func;                                                \
-        ret;                                                 \
-        }                                                    \
+#define ERROR_IF_FUNC(exp, errstr, func, ret)                  \
+  do {                                                         \
+     if (exp) {                                                \
+        char tmp[64];                                          \
+        esyslog("[%s,%d]: " errstr ": %s", __FILE__, __LINE__, \
+                strerror_r(errno, tmp, sizeof(tmp)));          \
+        func;                                                  \
+        ret;                                                   \
+        }                                                      \
   } while (0)
 
 
diff --git a/config.c b/config.c
index 050453c..6743e9c 100644
--- a/config.c
+++ b/config.c
@@ -17,6 +17,9 @@ cSatipConfig::cSatipConfig(void)
   ciExtensionM(0),
   eitScanM(1),
   useBytesM(1),
+  portRangeStartM(0),
+  portRangeStopM(0),
+  transportModeM(eTransportModeUnicast),
   detachedModeM(false),
   disableServerQuirksM(false),
   useSingleModelServersM(false)
diff --git a/config.h b/config.h
index b7745cf..45503f6 100644
--- a/config.h
+++ b/config.h
@@ -19,6 +19,9 @@ private:
   unsigned int ciExtensionM;
   unsigned int eitScanM;
   unsigned int useBytesM;
+  unsigned int portRangeStartM;
+  unsigned int portRangeStopM;
+  unsigned int transportModeM;
   bool detachedModeM;
   bool disableServerQuirksM;
   bool useSingleModelServersM;
@@ -34,6 +37,12 @@ public:
     eOperatingModeHigh,
     eOperatingModeCount
   };
+  enum eTransportMode {
+    eTransportModeUnicast = 0,
+    eTransportModeMulticast,
+    eTransportModeRtpOverTcp,
+    eTransportModeCount
+  };
   enum eTraceMode {
     eTraceModeNormal  = 0x0000,
     eTraceModeDebug1  = 0x0001,
@@ -67,6 +76,10 @@ public:
   int GetCICAM(unsigned int indexP) const;
   unsigned int GetEITScan(void) const { return eitScanM; }
   unsigned int GetUseBytes(void) const { return useBytesM; }
+  unsigned int GetTransportMode(void) const { return transportModeM; }
+  bool IsTransportModeUnicast(void) const { return (transportModeM == eTransportModeUnicast); }
+  bool IsTransportModeRtpOverTcp(void) const { return (transportModeM == eTransportModeRtpOverTcp); }
+  bool IsTransportModeMulticast(void) const { return (transportModeM == eTransportModeMulticast); }
   bool GetDetachedMode(void) const { return detachedModeM; }
   bool GetDisableServerQuirks(void) const { return disableServerQuirksM; }
   bool GetUseSingleModelServers(void) const { return useSingleModelServersM; }
@@ -74,6 +87,8 @@ public:
   int GetDisabledSources(unsigned int indexP) const;
   unsigned int GetDisabledFiltersCount(void) const;
   int GetDisabledFilters(unsigned int indexP) const;
+  unsigned int GetPortRangeStart(void) const { return portRangeStartM; }
+  unsigned int GetPortRangeStop(void) const { return portRangeStopM; }
 
   void SetOperatingMode(unsigned int operatingModeP) { operatingModeM = operatingModeP; }
   void SetTraceMode(unsigned int modeP) { traceModeM = (modeP & eTraceModeMask); }
@@ -81,11 +96,14 @@ public:
   void SetCICAM(unsigned int indexP, int cicamP);
   void SetEITScan(unsigned int onOffP) { eitScanM = onOffP; }
   void SetUseBytes(unsigned int onOffP) { useBytesM = onOffP; }
+  void SetTransportMode(unsigned int transportModeP) { transportModeM = transportModeP; }
   void SetDetachedMode(bool onOffP) { detachedModeM = onOffP; }
   void SetDisableServerQuirks(bool onOffP) { disableServerQuirksM = onOffP; }
   void SetUseSingleModelServers(bool onOffP) { useSingleModelServersM = onOffP; }
   void SetDisabledSources(unsigned int indexP, int sourceP);
   void SetDisabledFilters(unsigned int indexP, int numberP);
+  void SetPortRangeStart(unsigned int rangeStartP) { portRangeStartM = rangeStartP; }
+  void SetPortRangeStop(unsigned int rangeStopP) { portRangeStopM = rangeStopP; }
 };
 
 extern cSatipConfig SatipConfig;
diff --git a/device.c b/device.c
index ad96434..ee8c1c5 100644
--- a/device.c
+++ b/device.c
@@ -120,8 +120,12 @@ cString cSatipDevice::GetSatipStatus(void)
             info = cString::sprintf("%sCardIndex: %d  HasLock: yes  Strength: %d  Quality: %d%s\n", *info, device->CardIndex(), device->SignalStrength(), device->SignalQuality(), live ? "  Live: yes" : "");
          else
             info = cString::sprintf("%sCardIndex: %d  HasLock: no\n", *info, device->CardIndex());
-         if (channel && channel->Number() > 0)
-            info = cString::sprintf("%sTransponder: %d  Channel: %s\n", *info, (channel && channel->Number() > 0) ? channel->Transponder() : 0, (channel && channel->Number() > 0) ? channel->Name() : "---");
+         if (channel) {
+            if (channel->Number() > 0 && device->Receiving())
+               info = cString::sprintf("%sTransponder: %d  Channel: %s\n", *info, channel->Transponder(), channel->Name());
+            else
+               info = cString::sprintf("%sTransponder: %d\n", *info, channel->Transponder());
+            }
          if (timers)
             info = cString::sprintf("%sRecording: %d timer%s\n", *info, timers, (timers > 1) ? "s" : "");
          info = cString::sprintf("%s\n", *info);
@@ -136,7 +140,7 @@ cString cSatipDevice::GetGeneralInformation(void)
 #if defined(APIVERSNUM) && APIVERSNUM >= 20301
   LOCK_CHANNELS_READ;
 #endif
-  return cString::sprintf("SAT>IP device: %d\nCardIndex: %d\nStream: %s\nSignal: %s\nStream bitrate: %s\n%sChannel: %s",
+  return cString::sprintf("SAT>IP device: %d\nCardIndex: %d\nStream: %s\nSignal: %s\nStream bitrate: %s\n%sChannel: %s\n",
                           deviceIndexM, CardIndex(),
                           pTunerM ? *pTunerM->GetInformation() : "",
                           pTunerM ? *pTunerM->GetSignalStatus() : "",
@@ -206,6 +210,8 @@ cString cSatipDevice::DeviceType(void) const
 cString cSatipDevice::DeviceName(void) const
 {
   debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  if (!Receiving())
+     return cString::sprintf("%s %d", *DeviceType(), deviceIndexM);
   return deviceNameM;
 }
 
@@ -355,6 +361,7 @@ bool cSatipDevice::SetChannelDevice(const cChannel *channelP, bool liveViewP)
      }
   else if (pTunerM) {
      pTunerM->SetSource(NULL, 0, NULL, deviceIndexM);
+     deviceNameM = cString::sprintf("%s %d", *DeviceType(), deviceIndexM);
      return true;
      }
   return false;
@@ -366,7 +373,7 @@ bool cSatipDevice::SetPid(cPidHandle *handleP, int typeP, bool onP)
   if (pTunerM && handleP && handleP->pid >= 0) {
      if (onP)
         return pTunerM->SetPid(handleP->pid, typeP, true);
-     else if (!handleP->used)
+     else if (!handleP->used && pSectionFilterHandlerM && !pSectionFilterHandlerM->Exists(handleP->pid))
         return pTunerM->SetPid(handleP->pid, typeP, false);
      }
   return true;
@@ -472,6 +479,13 @@ int cSatipDevice::GetCISlot(void)
   return slot;
 }
 
+cString cSatipDevice::GetTnrParameterString(void)
+{
+   if (channelM.Ca())
+      return GetTnrUrlParameters(&channelM);
+   return NULL;
+}
+
 bool cSatipDevice::IsIdle(void)
 {
   return !Receiving();
diff --git a/device.h b/device.h
index ad18e2f..983e24d 100644
--- a/device.h
+++ b/device.h
@@ -110,6 +110,7 @@ public:
   virtual int GetId(void);
   virtual int GetPmtPid(void);
   virtual int GetCISlot(void);
+  virtual cString GetTnrParameterString(void);
   virtual bool IsIdle(void);
 };
 
diff --git a/deviceif.h b/deviceif.h
index b67bfe1..9fe1636 100644
--- a/deviceif.h
+++ b/deviceif.h
@@ -16,6 +16,7 @@ public:
   virtual int GetId(void) = 0;
   virtual int GetPmtPid(void) = 0;
   virtual int GetCISlot(void) = 0;
+  virtual cString GetTnrParameterString(void) = 0;
   virtual bool IsIdle(void) = 0;
 
 private:
diff --git a/discover.c b/discover.c
index 990af68..7c128a8 100644
--- a/discover.c
+++ b/discover.c
@@ -32,7 +32,7 @@ bool cSatipDiscover::Initialize(cSatipDiscoverServers *serversP)
   if (instanceS) {
        if (serversP) {
           for (cSatipDiscoverServer *s = serversP->First(); s; s = serversP->Next(s))
-              instanceS->AddServer(s->IpAddress(), s->Model(), s->Description());
+              instanceS->AddServer(s->IpAddress(), s->IpPort(), s->Model(), s->Filters(), s->Description(), s->Quirk());
           }
      else
         instanceS->Activate();
@@ -47,6 +47,18 @@ void cSatipDiscover::Destroy(void)
      instanceS->Deactivate();
 }
 
+size_t cSatipDiscover::HeaderCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP)
+{
+  cSatipDiscover *obj = reinterpret_cast<cSatipDiscover *>(dataP);
+  size_t len = sizeP * nmembP;
+  debug16("%s len=%zu", __PRETTY_FUNCTION__, len);
+
+  if (obj && (len > 0))
+     obj->headerBufferM.Add(ptrP, len);
+
+  return len;
+}
+
 size_t cSatipDiscover::DataCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP)
 {
   cSatipDiscover *obj = reinterpret_cast<cSatipDiscover *>(dataP);
@@ -91,6 +103,7 @@ int cSatipDiscover::DebugCallback(CURL *handleP, curl_infotype typeP, char *data
 cSatipDiscover::cSatipDiscover()
 : cThread("SATIP discover"),
   mutexM(),
+  headerBufferM(),
   dataBufferM(),
   msearchM(*this),
   probeUrlListM(),
@@ -176,7 +189,9 @@ void cSatipDiscover::Fetch(const char *urlP)
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGFUNCTION, cSatipDiscover::DebugCallback);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGDATA, this);
 
-     // Set callback
+     // Set header and data callbacks
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, cSatipDiscover::HeaderCallback);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, this);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, cSatipDiscover::DataCallback);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, this);
 
@@ -199,7 +214,8 @@ void cSatipDiscover::Fetch(const char *urlP)
      SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_RESPONSE_CODE, &rc);
      SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_PRIMARY_IP, &addr);
      if (rc == 200) {
-        ParseDeviceInfo(addr);
+        ParseDeviceInfo(addr, ParseRtspPort());
+        headerBufferM.Reset();
         dataBufferM.Reset();
         }
      else
@@ -207,9 +223,32 @@ void cSatipDiscover::Fetch(const char *urlP)
      }
 }
 
-void cSatipDiscover::ParseDeviceInfo(const char *addrP)
+int cSatipDiscover::ParseRtspPort(void)
 {
-  debug1("%s (%s)", __PRETTY_FUNCTION__, addrP);
+  debug1("%s", __PRETTY_FUNCTION__);
+  char *s, *p = headerBufferM.Data();
+  char *r = strtok_r(p, "\r\n", &s);
+  int port = SATIP_DEFAULT_RTSP_PORT;
+
+  while (r) {
+        debug16("%s (%zu): %s", __PRETTY_FUNCTION__, headerBufferM.Size(), r);
+        r = skipspace(r);
+        if (strstr(r, "X-SATIP-RTSP-Port")) {
+           int tmp = -1;
+           if (sscanf(r, "X-SATIP-RTSP-Port:%11d", &tmp) == 1) {
+              port = tmp;
+              break;
+              }
+           }
+        r = strtok_r(NULL, "\r\n", &s);
+        }
+
+  return port;
+}
+
+void cSatipDiscover::ParseDeviceInfo(const char *addrP, const int portP)
+{
+  debug1("%s (%s, %d)", __PRETTY_FUNCTION__, addrP, portP);
   const char *desc = NULL, *model = NULL;
 #ifdef USE_TINYXML
   TiXmlDocument doc;
@@ -232,12 +271,12 @@ void cSatipDiscover::ParseDeviceInfo(const char *addrP)
         model = modelNode.text().as_string("DVBS2-1");
      }
 #endif
-  AddServer(addrP, model, desc);
+  AddServer(addrP, portP, model, NULL, desc, cSatipServer::eSatipQuirkNone);
 }
 
-void cSatipDiscover::AddServer(const char *addrP, const char *modelP, const char * descP)
+void cSatipDiscover::AddServer(const char *addrP, const int portP, const char *modelP, const char *filtersP, const char *descP, const int quirkP)
 {
-  debug1("%s (%s, %s, %s)", __PRETTY_FUNCTION__, addrP, modelP, descP);
+  debug1("%s (%s, %d, %s, %s, %s, %d)", __PRETTY_FUNCTION__, addrP, portP, modelP, filtersP, descP, quirkP);
   cMutexLock MutexLock(&mutexM);
   if (SatipConfig.GetUseSingleModelServers() && modelP && !isempty(modelP)) {
      int n = 0;
@@ -246,9 +285,9 @@ void cSatipDiscover::AddServer(const char *addrP, const char *modelP, const char
      while (r) {
            r = skipspace(r);
            cString desc = cString::sprintf("%s #%d", !isempty(descP) ? descP : "MyBrokenHardware", n++);
-           cSatipServer *tmp = new cSatipServer(addrP, r, desc);
+           cSatipServer *tmp = new cSatipServer(addrP, portP, r, filtersP, desc, quirkP);
            if (!serversM.Update(tmp)) {
-              info("Adding server '%s|%s|%s' CI: %s Quirks: %s", tmp->Address(), tmp->Model(), tmp->Description(), tmp->HasCI() ? "yes" : "no", tmp->HasQuirk() ? tmp->Quirks() : "none");
+              info("Adding server '%s|%s|%s' Filters: %s CI: %s Quirks: %s", tmp->Address(), tmp->Model(), tmp->Description(), !isempty(tmp->Filters()) ? tmp->Filters() : "none", tmp->HasCI() ? "yes" : "no", tmp->HasQuirk() ? tmp->Quirks() : "none");
               serversM.Add(tmp);
               }
            else
@@ -258,9 +297,9 @@ void cSatipDiscover::AddServer(const char *addrP, const char *modelP, const char
      FREE_POINTER(p);
      }
   else {
-     cSatipServer *tmp = new cSatipServer(addrP, modelP, descP);
+     cSatipServer *tmp = new cSatipServer(addrP, portP, modelP, filtersP, descP, quirkP);
      if (!serversM.Update(tmp)) {
-        info("Adding server '%s|%s|%s' CI: %s Quirks: %s", tmp->Address(), tmp->Model(), tmp->Description(), tmp->HasCI() ? "yes" : "no", tmp->HasQuirk() ? tmp->Quirks() : "none");
+        info("Adding server '%s|%s|%s' Filters: %s CI: %s Quirks: %s", tmp->Address(), tmp->Model(), tmp->Description(), !isempty(tmp->Filters()) ? tmp->Filters() : "none", tmp->HasCI() ? "yes" : "no", tmp->HasQuirk() ? tmp->Quirks() : "none");
         serversM.Add(tmp);
         }
      else
@@ -317,6 +356,13 @@ cString cSatipDiscover::GetServerList(void)
   return serversM.List();
 }
 
+void cSatipDiscover::ActivateServer(cSatipServer *serverP, bool onOffP)
+{
+  debug16("%s (, %d)", __PRETTY_FUNCTION__, onOffP);
+  cMutexLock MutexLock(&mutexM);
+  serversM.Activate(serverP, onOffP);
+}
+
 void cSatipDiscover::AttachServer(cSatipServer *serverP, int deviceIdP, int transponderP)
 {
   debug16("%s (, %d, %d)", __PRETTY_FUNCTION__, deviceIdP, transponderP);
@@ -352,6 +398,13 @@ cString cSatipDiscover::GetServerAddress(cSatipServer *serverP)
   return serversM.GetAddress(serverP);
 }
 
+int cSatipDiscover::GetServerPort(cSatipServer *serverP)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  return serversM.GetPort(serverP);
+}
+
 int cSatipDiscover::NumProvidedSystems(void)
 {
   debug16("%s", __PRETTY_FUNCTION__);
diff --git a/discover.h b/discover.h
index 5942e42..52c3d30 100644
--- a/discover.h
+++ b/discover.h
@@ -21,16 +21,22 @@
 
 class cSatipDiscoverServer : public cListObject {
 private:
+  int ipPortM;
+  int quirkM;
   cString ipAddressM;
   cString descriptionM;
   cString modelM;
+  cString filtersM;
 public:
-  cSatipDiscoverServer(const char *ipAddressP, const char *modelP, const char *descriptionP)
+  cSatipDiscoverServer(const char *ipAddressP, const int ipPortP, const char *modelP, const char *filtersP, const char *descriptionP, const int quirkP)
   {
-    ipAddressM = ipAddressP; modelM = modelP; descriptionM = descriptionP;
+    ipAddressM = ipAddressP; ipPortM = ipPortP; modelM = modelP; filtersM = filtersP; descriptionM = descriptionP; quirkM = quirkP;
   }
+  int IpPort(void)              { return ipPortM; }
+  int Quirk(void)               { return quirkM; }
   const char *IpAddress(void)   { return *ipAddressM; }
   const char *Model(void)       { return *modelM; }
+  const char *Filters(void)     { return *filtersM; }
   const char *Description(void) { return *descriptionM; }
 };
 
@@ -47,9 +53,11 @@ private:
     eCleanupTimeoutMs = 124000 // in milliseoonds
   };
   static cSatipDiscover *instanceS;
+  static size_t HeaderCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP);
   static size_t DataCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP);
   static int    DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP);
   cMutex mutexM;
+  cSatipMemoryBuffer headerBufferM;
   cSatipMemoryBuffer dataBufferM;
   cSatipMsearch msearchM;
   cStringList probeUrlListM;
@@ -59,8 +67,9 @@ private:
   cSatipServers serversM;
   void Activate(void);
   void Deactivate(void);
-  void ParseDeviceInfo(const char *addrP);
-  void AddServer(const char *addrP, const char *modelP, const char *descP);
+  int ParseRtspPort(void);
+  void ParseDeviceInfo(const char *addrP, const int portP);
+  void AddServer(const char *addrP, const int portP, const char *modelP, const char *filtersP, const char *descP, const int quirkP);
   void Fetch(const char *urlP);
   // constructor
   cSatipDiscover();
@@ -83,11 +92,13 @@ public:
   cSatipServer *GetServer(cSatipServer *serverP);
   cSatipServers *GetServers(void);
   cString GetServerString(cSatipServer *serverP);
+  void ActivateServer(cSatipServer *serverP, bool onOffP);
   void AttachServer(cSatipServer *serverP, int deviceIdP, int transponderP);
   void DetachServer(cSatipServer *serverP, int deviceIdP, int transponderP);
   bool IsServerQuirk(cSatipServer *serverP, int quirkP);
   bool HasServerCI(cSatipServer *serverP);
   cString GetServerAddress(cSatipServer *serverP);
+  int GetServerPort(cSatipServer *serverP);
   cString GetServerList(void);
   int NumProvidedSystems(void);
 
diff --git a/msearch.c b/msearch.c
index f5edafc..b137a05 100644
--- a/msearch.c
+++ b/msearch.c
@@ -29,7 +29,7 @@ cSatipMsearch::cSatipMsearch(cSatipDiscoverIf &discoverP)
      memset(bufferM, 0, bufferLenM);
   else
      error("Cannot create Msearch buffer!");
-  if (!Open(eDiscoveryPort))
+  if (!Open(eDiscoveryPort, true))
      error("Cannot open Msearch port!");
 }
 
@@ -100,6 +100,11 @@ void cSatipMsearch::Process(void)
      }
 }
 
+void cSatipMsearch::Process(unsigned char *dataP, int lengthP)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+}
+
 cString cSatipMsearch::ToString(void) const
 {
   return "MSearch";
diff --git a/msearch.h b/msearch.h
index db00c86..73bd375 100644
--- a/msearch.h
+++ b/msearch.h
@@ -34,6 +34,7 @@ public:
 public:
   virtual int GetFd(void);
   virtual void Process(void);
+  virtual void Process(unsigned char *dataP, int lengthP);
   virtual cString ToString(void) const;
 };
 
diff --git a/param.c b/param.c
index c71902d..5104559 100644
--- a/param.c
+++ b/param.c
@@ -163,34 +163,344 @@ cString GetTransponderUrlParameters(const cChannel *channelP)
        dtp.SetModulation(QPSK);
        dtp.SetRollOff(ROLLOFF_35);
        }
+     if ((channelP->Rid() % 100) > 0)
+                q += snprintf(q,       STBUFLEFT, "&fe=%d",           channelP->Rid() % 100);
+     ST(" S *") q += snprintf(q,       STBUFLEFT, "src=%d&",          ((src > 0) && (src <= 255)) ? src : 1);
                 q += snprintf(q,       STBUFLEFT, "freq=%s",          *dtoa(freq, "%lg"));
-     ST(" S *") q += snprintf(q,       STBUFLEFT, "&src=%d",          ((src > 0) && (src <= 255)) ? src : 1);
-     ST(" S *") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
-     ST("C  1") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
      ST(" S *") q += snprintf(q,       STBUFLEFT, "&pol=%c",          tolower(dtp.Polarization()));
-     ST("C T2") q += snprintf(q,       STBUFLEFT, "&plp=%d",          dtp.StreamId());
-     ST("  T2") q += snprintf(q,       STBUFLEFT, "&t2id=%d",         dtp.T2SystemId());
+     ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.RollOff(),      SatipRollOffValues);
      ST("C  2") q += snprintf(q,       STBUFLEFT, "&c2tft=%d",        C2TuningFrequencyType);
-     ST("C  2") q += snprintf(q,       STBUFLEFT, "&ds=%d",           DataSlice);
-     ST("C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Inversion(),    SatipInversionValues);
-     ST("  T2") q += PrintUrlString(q, STBUFLEFT, dtp.SisoMiso(),     SatipSisoMisoValues);
      ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Bandwidth(),    SatipBandwidthValues);
      ST("C  2") q += PrintUrlString(q, STBUFLEFT, dtp.Bandwidth(),    SatipBandwidthValues);
-     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Guard(),        SatipGuardValues);
-     ST("CST*") q += PrintUrlString(q, STBUFLEFT, dtp.CoderateH(),    SatipCodeRateValues);
-     ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.Pilot(),        SatipPilotValues);
-     ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
-     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
-     ST("C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
-     ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.RollOff(),      SatipRollOffValues);
      ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesSat);
      ST("C  *") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesCable);
      ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesTerrestrial);
      ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Transmission(), SatipTransmissionValues);
-     if ((channelP->Rid() % 100) > 0)
-                snprintf(q,            STBUFLEFT, "&fe=%d",           channelP->Rid() % 100);
+     ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
+     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
+     ST("C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
+     ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.Pilot(),        SatipPilotValues);
+     ST(" S *") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
+     ST("C  1") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
+     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Guard(),        SatipGuardValues);
+     ST("CST*") q += PrintUrlString(q, STBUFLEFT, dtp.CoderateH(),    SatipCodeRateValues);
+     ST("C  2") q += snprintf(q,       STBUFLEFT, "&ds=%d",           DataSlice);
+     ST("C T2") q += snprintf(q,       STBUFLEFT, "&plp=%d",          dtp.StreamId());
+     ST("  T2") q += snprintf(q,       STBUFLEFT, "&t2id=%d",         dtp.T2SystemId());
+     ST("  T2") q += PrintUrlString(q, STBUFLEFT, dtp.SisoMiso(),     SatipSisoMisoValues);
+     ST("C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Inversion(),    SatipInversionValues);
 #undef ST
      return buffer;
      }
   return NULL;
 }
+
+cString GetTnrUrlParameters(const cChannel *channelP)
+{
+  if (channelP) {
+     cDvbTransponderParameters dtp(channelP->Parameters());
+     eTrackType track = cDevice::PrimaryDevice()->GetCurrentAudioTrack();
+
+     // TunerType: Byte;
+     //   0 = cable, 1 = satellite, 2 = terrestrial, 3 = atsc, 4 = iptv, 5 = stream (URL, DVBViewer GE)
+     int TunerType = 0;
+     if (channelP->IsCable())
+        TunerType = 0;
+     else if (channelP->IsSat())
+        TunerType = 1;
+     else if (channelP->IsTerr())
+        TunerType = 2;
+     else if (channelP->IsAtsc())
+        TunerType = 3;
+
+     // Frequency: DWord;
+     //   DVB-S: MHz if < 1000000, kHz if >= 1000000
+     //   DVB-T/C, ATSC: kHz
+     //   IPTV: IP address Byte3.Byte2.Byte1.Byte0
+     int Frequency = channelP->Frequency() / 1000;
+
+     // Symbolrate: DWord;
+     //   DVB S/C: in kSym/s
+     //   DVB-T, ATSC: 0
+     //   IPTV: Port
+     int Symbolrate = (channelP->IsSat() || channelP->IsCable()) ? channelP->Srate() : 0;
+
+     // LNB_LOF: Word;
+     //   DVB-S: Local oscillator frequency of the LNB
+     //   DVB-T/C, ATSC: 0
+     //   IPTV: Byte0 and Byte1 of Source IP
+     int LNB_LOF = channelP->IsSat() ? Setup.LnbSLOF : 0;
+
+     // Tone: Byte;
+     //   0 = off, 1 = 22 khz
+     int Tone = (channelP->Frequency() < Setup.LnbSLOF) ? 0 : 1;
+
+     // Polarity: Byte;
+     //   DVB-S polarity: 0 = horizontal, 1 = vertical, 2 = circular left, 3 = circular right
+     //   DVB-C modulation: 0 = Auto, 1 = 16QAM, 2 = 32QAM, 3 = 64QAM, 4 = 128QAM, 5 = 256 QAM
+     //   DVB-T bandwidth: 0 = 6 MHz, 1 = 7 MHz, 2 = 8 MHz
+     //   IPTV: Byte3 of SourceIP
+     int Polarity = 0;
+     if (channelP->IsSat()) {
+        switch (tolower(dtp.Polarization())) {
+          case 'h':
+               Polarity = 0;
+               break;
+          case 'v':
+               Polarity = 1;
+               break;
+          case 'l':
+               Polarity = 2;
+               break;
+          case 'r':
+               Polarity = 3;
+               break;
+          default:
+               break;
+          }
+        }
+     else if (channelP->IsCable()) {
+        switch (dtp.Modulation()) {
+          case 999:
+               Polarity = 0;
+               break;
+          case 16:
+               Polarity = 1;
+               break;
+          case 32:
+               Polarity = 2;
+               break;
+          case 64:
+               Polarity = 3;
+               break;
+          case 128:
+               Polarity = 4;
+               break;
+          case 256:
+               Polarity = 5;
+               break;
+          default:
+               break;
+          }
+        }
+     else if (channelP->IsTerr()) {
+        switch (dtp.Bandwidth()) {
+          case 6:
+               Polarity = 0;
+               break;
+          case 7:
+               Polarity = 1;
+               break;
+          case 8:
+               Polarity = 2;
+               break;
+          default:
+               break;
+          }
+        }
+
+     // DiSEqC: Byte;
+     //   0 = None
+     //   1 = Pos A (mostly translated to PosA/OptA)
+     //   2 = Pos B (mostly translated to PosB/OptA)
+     //   3 = PosA/OptA
+     //   4 = PosB/OptA
+     //   5 = PosA/OptB
+     //   6 = PosB/OptB
+     //   7 = Preset Position (DiSEqC 1.2, see DiSEqCExt)
+     //   8 = Angular Position (DiSEqC 1.2, see DiSEqCExt)
+     //   9 = DiSEqC Command Sequence (see DiSEqCExt)
+     int DiSEqC = 0;
+
+     // FEC: Byte;
+     //   0 = Auto
+     //   1 = 1/2
+     //   2 = 2/3
+     //   3 = 3/4
+     //   4 = 5/6
+     //   5 = 7/8
+     //   6 = 8/9
+     //   7 = 3/5
+     //   8 = 4/5
+     //   9 = 9/10
+     //   IPTV: Byte2 of SourceIP
+     //   DVB C/T, ATSC: 0
+     int FEC = 0;
+     if (channelP->IsSat()) {
+        switch (dtp.CoderateH()) {
+          case 999:
+               FEC = 0;
+               break;
+          case 12:
+               FEC = 1;
+               break;
+          case 23:
+               FEC = 2;
+               break;
+          case 34:
+               FEC = 3;
+               break;
+          case 56:
+               FEC = 4;
+               break;
+          case 78:
+               FEC = 5;
+               break;
+          case 89:
+               FEC = 6;
+               break;
+          case 35:
+               FEC = 7;
+               break;
+          case 45:
+               FEC = 8;
+               break;
+          case 910:
+               FEC = 9;
+               break;
+          default:
+               break;
+          }
+        }
+
+     // Audio_PID: Word;
+     int Audio_PID = channelP->Apid(0);
+     if (IS_AUDIO_TRACK(track))
+        Audio_PID = channelP->Apid(int(track - ttAudioFirst));
+     else if (IS_DOLBY_TRACK(track))
+        Audio_PID = channelP->Dpid(int(track - ttDolbyFirst));
+
+     // Video_PID: Word;
+     int Video_PID = channelP->Vpid();
+
+     // PMT_PID: Word;
+     int PMT_PID = channelP->Ppid();
+
+     // Service_ID: Word;
+     int Service_ID = channelP->Sid();
+
+     // SatModulation: Byte;
+     //   Bit 0..1: satellite modulation. 0 = Auto, 1 = QPSK, 2 = 8PSK, 3 = 16QAM or APSK for DVB-S2
+     //   Bit 2: modulation system. 0 = DVB-S/T/C, 1 = DVB-S2/T2/C2
+     //   Bit 3..4: DVB-S2: roll-off. 0 = 0.35, 1 = 0.25, 2 = 0.20, 3 = reserved
+     //   Bit 5..6: spectral inversion, 0 = undefined, 1 = auto, 2 = normal, 3 = inverted
+     //   Bit 7: DVB-S2: pilot symbols, 0 = off, 1 = on
+     //          DVB-T2: DVB-T2 Lite, 0 = off, 1 = on
+     int SatModulation = 0;
+     if (channelP->IsSat() && dtp.System()) {
+        switch (dtp.Modulation()) {
+          case 999:
+               SatModulation |= (0 & 0x3) << 0;
+               break;
+          case 2:
+               SatModulation |= (1 & 0x3) << 0;
+               break;
+          case 5:
+               SatModulation |= (2 & 0x3) << 0;
+               break;
+          case 6:
+               SatModulation |= (3 & 0x3) << 0;
+               break;
+          default:
+               break;
+          }
+        }
+     SatModulation |= (dtp.System() & 0x1) << 2;
+     if (channelP->IsSat() && dtp.System()) {
+        switch (dtp.RollOff()) {
+          case 35:
+               SatModulation |= (0 & 0x3) << 3;
+               break;
+          case 25:
+               SatModulation |= (1 & 0x3) << 3;
+               break;
+          case 20:
+               SatModulation |= (2 & 0x3) << 3;
+               break;
+          default:
+               break;
+          }
+        }
+     switch (dtp.Inversion()) {
+       case 999:
+           SatModulation |= (1 & 0x3) << 5;
+           break;
+       case 0:
+           SatModulation |= (2 & 0x3) << 5;
+           break;
+       case 1:
+           SatModulation |= (3 & 0x3) << 5;
+           break;
+       default:
+           break;
+       }
+     if (channelP->IsSat() && dtp.System()) {
+        switch (dtp.Pilot()) {
+          case 0:
+               SatModulation |= (0 & 0x1) << 7;
+               break;
+          case 1:
+               SatModulation |= (1 & 0x1) << 7;
+               break;
+          default:
+               break;
+          }
+        }
+
+     // DiSEqCExt: Word;
+     //   DiSEqC Extension, meaning depends on DiSEqC
+     //   DiSEqC = 0..6: 0
+     //   DiSEqC = 7: Preset Position (DiSEqC 1.2)
+     //   DiSEqC = 8: Orbital Position (DiSEqC 1.2, USALS, for calculating motor angle)
+     //               Same format as OrbitalPos above
+     //   DiSEQC = 9: Orbital Position referencing DiSEqC sequence defined in DiSEqC.xml/ini
+     //               Same format as OrbitalPos above
+     int DiSEqCExt = 0;
+
+     // Flags: Byte;
+     //   Bit 0: 1 = encrypted channel
+     //   Bit 1: reserved, set to 0
+     //   Bit 2: 1 = channel broadcasts RDS data
+     //   Bit 3: 1 = channel is a video service (even if the Video PID is temporarily = 0)
+     //   Bit 4: 1 = channel is an audio service (even if the Audio PID is temporarily = 0)
+     //   Bit 5: 1 = audio has a different samplerate than 48 KHz
+     //   Bit 6: 1 = bandstacking, internally polarisation is always set to H
+     //   Bit 7: 1 = channel entry is an additional audio track of the preceding
+     //              channel with bit 7 = 0
+     int Flags = (channelP->Ca() > 0xFF) ? 1 : 0;
+
+     // ChannelGroup: Byte;
+     //   0 = Group A, 1 = Group B, 2 = Group C etc.
+     int ChannelGroup = 0;
+
+     // TransportStream_ID: Word;
+     int TransportStream_ID = channelP->Tid();
+
+     // OriginalNetwork_ID: Word;
+     int OriginalNetwork_ID = channelP->Nid();
+
+     // Substream: Word;
+     //   DVB-S/C/T, ATSC, IPTV: 0
+     //   DVB-T2: 0 = PLP_ID not set, 1..256: PLP_ID + 1, 257... reserved
+     int Substream = (channelP->IsTerr() && dtp.System()) ? dtp.StreamId() - 1 : 0;
+
+     // OrbitalPos: Word;
+     //   DVB-S: orbital position x 10, 0 = undefined, 1..1800 east, 1801..3599 west (1°W = 3599)
+     //   DVB-C: 4000..4999
+     //   DVB-T: 5000..5999
+     //   ATSC:  6000..6999
+     //   IPTV:  7000..7999
+     //   Stream: 8000..8999
+     int OrbitalPos = 0;
+     if (channelP->IsSat()) {
+        OrbitalPos = cSource::Position(channelP->Source());
+        if (OrbitalPos != 3600)
+           OrbitalPos += 1800;
+        }
+
+     return cString::sprintf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+                             TunerType, Frequency, Symbolrate, LNB_LOF, Tone, Polarity, DiSEqC, FEC, Audio_PID, Video_PID, PMT_PID, Service_ID,
+                             SatModulation, DiSEqCExt, Flags, ChannelGroup, TransportStream_ID, OriginalNetwork_ID, Substream, OrbitalPos);
+     }
+  return NULL;
+}
diff --git a/param.h b/param.h
index ae68578..82de1b3 100644
--- a/param.h
+++ b/param.h
@@ -11,5 +11,6 @@
 #include "common.h"
 
 cString GetTransponderUrlParameters(const cChannel *channelP);
+cString GetTnrUrlParameters(const cChannel *channelP);
 
 #endif // __SATIP_PARAM_H
diff --git a/po/ca_ES.po b/po/ca_ES.po
index 653f8fe..5627c62 100644
--- a/po/ca_ES.po
+++ b/po/ca_ES.po
@@ -1,14 +1,14 @@
 # VDR plugin language source file.
-# Copyright (C) 2007-2015 Rolf Ahrenberg
+# Copyright (C) 2007-2016 Rolf Ahrenberg
 # This file is distributed under the same license as the satip package.
 # Gabriel Bonich, 2014-2015
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: vdr-satip 2.2.3\n"
+"Project-Id-Version: vdr-satip 2.2.4\n"
 "Report-Msgid-Bugs-To: <see README>\n"
-"POT-Creation-Date: 2015-04-26 04:26+0300\n"
-"PO-Revision-Date: 2015-04-26 04:26+0300\n"
+"POT-Creation-Date: 2016-12-18 12:18+0200\n"
+"PO-Revision-Date: 2016-12-18 12:18+0200\n"
 "Last-Translator: Gabriel Bonich <gbonich at gmail.com>\n"
 "Language-Team: Catalan <vdr at linuxtv.org>\n"
 "Language: ca\n"
@@ -85,6 +85,15 @@ msgstr "Normal"
 msgid "high"
 msgstr "Alt"
 
+msgid "Unicast"
+msgstr ""
+
+msgid "Multicast"
+msgstr ""
+
+msgid "RTP-over-TCP"
+msgstr ""
+
 msgid "Button$Devices"
 msgstr "Dispositius"
 
@@ -178,6 +187,15 @@ msgstr "Filtra"
 msgid "Define an ill-behaving filter to be blacklisted."
 msgstr "Definir un filtre mal comportar a la llista negra."
 
+msgid "Transport mode"
+msgstr ""
+
+msgid ""
+"Define which transport mode shall be used.\n"
+"\n"
+"Unicast, Multicast, RTP-over-TCP"
+msgstr ""
+
 msgid "Active SAT>IP servers:"
 msgstr "Activa SAT>IP servers:"
 
diff --git a/po/de_DE.po b/po/de_DE.po
index b1794cd..e7c66fd 100644
--- a/po/de_DE.po
+++ b/po/de_DE.po
@@ -1,14 +1,14 @@
 # VDR plugin language source file.
-# Copyright (C) 2007-2015 Rolf Ahrenberg
+# Copyright (C) 2007-2016 Rolf Ahrenberg
 # This file is distributed under the same license as the satip package.
-# Frank Neumann, 2014-2015
+# Frank Neumann, 2014-2016
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: vdr-satip 2.2.3\n"
+"Project-Id-Version: vdr-satip 2.2.4\n"
 "Report-Msgid-Bugs-To: <see README>\n"
-"POT-Creation-Date: 2015-04-26 04:26+0300\n"
-"PO-Revision-Date: 2015-04-26 04:26+0300\n"
+"POT-Creation-Date: 2016-12-18 12:18+0200\n"
+"PO-Revision-Date: 2016-12-18 12:18+0200\n"
 "Last-Translator: Frank Neumann <fnu at yavdr.org>\n"
 "Language-Team: German <vdr at linuxtv.org>\n"
 "Language: de\n"
@@ -85,11 +85,20 @@ msgstr "normal"
 msgid "high"
 msgstr "hoch"
 
+msgid "Unicast"
+msgstr "Unicast"
+
+msgid "Multicast"
+msgstr "Multicast"
+
+msgid "RTP-over-TCP"
+msgstr "RTP-over-TCP"
+
 msgid "Button$Devices"
 msgstr "Geräte"
 
 msgid "Operating mode"
-msgstr "Betriebsmodus"
+msgstr "Betriebsart"
 
 msgid ""
 "Define the used operating mode for all SAT>IP devices:\n"
@@ -99,7 +108,7 @@ msgid ""
 "normal - devices are working within normal parameters\n"
 "high - devices are working at the highest priority"
 msgstr ""
-"Bestimme den Betriebsmodus für alle SAT>IP Geräte:\n"
+"Bestimme die Betriebsart für alle SAT>IP Geräte:\n"
 "\n"
 "aus - Geräte sind abgeschaltet\n"
 "niedrig - Geräte arbeiten mit geringster Priorität\n"
@@ -170,13 +179,25 @@ msgid ""
 msgstr ""
 "Bestimme die Anzahl der Abschnittsfilter die deaktiviert werden sollen.\n"
 "\n"
-"Bestimmte Abschnittsfilter können unerwünschtes Verhalten mit VDR, z.B. falsche Zeit-Synchronisation, verursachen. Durch das Ausblenden einzelner Filter können nützliche Daten dieser Abschnitte für den VDR erhalten werden."
+"Bestimmte Abschnittsfilter können unerwünschtes Verhalten mit VDR, z.B. falsche Zeit-Synchronisation, verursachen. Durch das Ausblenden einzelner Filter können nützliche Daten dieser Abschnitte für den VDR erhalten bleiben."
 
 msgid "Filter"
 msgstr "Filter"
 
 msgid "Define an ill-behaving filter to be blacklisted."
-msgstr "Bestimme einen fehlerhaften Filter der ausgeblendet werden soll."
+msgstr "Bestimme fehlerhafte Filter die ausgeblendet werden sollen."
+
+msgid "Transport mode"
+msgstr "Übertragungsart"
+
+msgid ""
+"Define which transport mode shall be used.\n"
+"\n"
+"Unicast, Multicast, RTP-over-TCP"
+msgstr ""
+"Lege die gewünschte Übertragungsart fest.\n"
+"\n"
+"Unicast, Multicast, RTP-over-TCP"
 
 msgid "Active SAT>IP servers:"
 msgstr "Aktive SAT>IP Server:"
diff --git a/po/es_ES.po b/po/es_ES.po
index dbc27d3..39353be 100644
--- a/po/es_ES.po
+++ b/po/es_ES.po
@@ -1,14 +1,14 @@
 # VDR plugin language source file.
-# Copyright (C) 2007-2015 Rolf Ahrenberg
+# Copyright (C) 2007-2016 Rolf Ahrenberg
 # This file is distributed under the same license as the satip package.
 # Gabriel Bonich, 2014-2015
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: vdr-satip 2.2.3\n"
+"Project-Id-Version: vdr-satip 2.2.4\n"
 "Report-Msgid-Bugs-To: <see README>\n"
-"POT-Creation-Date: 2015-04-26 04:26+0300\n"
-"PO-Revision-Date: 2015-04-26 04:26+0300\n"
+"POT-Creation-Date: 2016-12-18 12:18+0200\n"
+"PO-Revision-Date: 2016-12-18 12:18+0200\n"
 "Last-Translator: Gabriel Bonich <gbonich at gmail.com>\n"
 "Language-Team: Spanish <vdr at linuxtv.org>\n"
 "Language: es\n"
@@ -85,6 +85,15 @@ msgstr "Normal"
 msgid "high"
 msgstr "Alto"
 
+msgid "Unicast"
+msgstr ""
+
+msgid "Multicast"
+msgstr ""
+
+msgid "RTP-over-TCP"
+msgstr ""
+
 msgid "Button$Devices"
 msgstr "Dispositivos"
 
@@ -178,6 +187,15 @@ msgstr "Filtra"
 msgid "Define an ill-behaving filter to be blacklisted."
 msgstr "Define un filtro para poner en la lista negra."
 
+msgid "Transport mode"
+msgstr ""
+
+msgid ""
+"Define which transport mode shall be used.\n"
+"\n"
+"Unicast, Multicast, RTP-over-TCP"
+msgstr ""
+
 msgid "Active SAT>IP servers:"
 msgstr "Activa SAT>IP servers:"
 
diff --git a/po/fi_FI.po b/po/fi_FI.po
index fbad1b1..aa8e173 100644
--- a/po/fi_FI.po
+++ b/po/fi_FI.po
@@ -1,14 +1,14 @@
 # VDR plugin language source file.
-# Copyright (C) 2007-2015 Rolf Ahrenberg
+# Copyright (C) 2007-2016 Rolf Ahrenberg
 # This file is distributed under the same license as the satip package.
-# Rolf Ahrenberg, 2015
+# Rolf Ahrenberg, 2015-2016
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: vdr-satip 2.2.3\n"
+"Project-Id-Version: vdr-satip 2.2.4\n"
 "Report-Msgid-Bugs-To: <see README>\n"
-"POT-Creation-Date: 2015-04-26 04:26+0300\n"
-"PO-Revision-Date: 2015-04-26 04:26+0300\n"
+"POT-Creation-Date: 2016-12-18 12:18+0200\n"
+"PO-Revision-Date: 2016-12-18 12:18+0200\n"
 "Last-Translator: Rolf Ahrenberg\n"
 "Language-Team: Finnish <vdr at linuxtv.org>\n"
 "Language: fi\n"
@@ -85,6 +85,15 @@ msgstr "normaali"
 msgid "high"
 msgstr "korkea"
 
+msgid "Unicast"
+msgstr "Unicast"
+
+msgid "Multicast"
+msgstr "Multicast"
+
+msgid "RTP-over-TCP"
+msgstr "RTP-over-TCP"
+
 msgid "Button$Devices"
 msgstr "Laitteet"
 
@@ -177,6 +186,18 @@ msgstr "Suodatin"
 msgid "Define an ill-behaving filter to be blacklisted."
 msgstr "Määrittele käytöstä poistettava suodatin, joka lisätään mustalle listalle."
 
+msgid "Transport mode"
+msgstr "Siirtoyhteystapa"
+
+msgid ""
+"Define which transport mode shall be used.\n"
+"\n"
+"Unicast, Multicast, RTP-over-TCP"
+msgstr ""
+"Määrittele käytettävä siirtoyhteystapa.\n"
+"\n"
+"Unicast, Multicast, RTP-over-TCP"
+
 msgid "Active SAT>IP servers:"
 msgstr "Aktiiviset SAT>IP-palvelimet:"
 
diff --git a/poller.c b/poller.c
index 4058321..eeff216 100644
--- a/poller.c
+++ b/poller.c
@@ -79,7 +79,7 @@ void cSatipPoller::Action(void)
   // Do the thread loop
   while (Running()) {
         int nfds = epoll_wait(fdM, events, eMaxFileDescriptors, -1);
-        ERROR_IF_FUNC((nfds == -1), "epoll_wait() failed", break, ;);
+        ERROR_IF_FUNC((nfds == -1 && errno != EINTR), "epoll_wait() failed", break, ;);
         for (int i = 0; i < nfds; ++i) {
             cSatipPollerIf* poll = reinterpret_cast<cSatipPollerIf *>(events[i].data.ptr);
             if (poll) {
diff --git a/pollerif.h b/pollerif.h
index 3447871..67d33fb 100644
--- a/pollerif.h
+++ b/pollerif.h
@@ -14,6 +14,7 @@ public:
   virtual ~cSatipPollerIf() {}
   virtual int GetFd(void) = 0;
   virtual void Process(void) = 0;
+  virtual void Process(unsigned char *dataP, int lengthP) = 0;
   virtual cString ToString(void) const = 0;
 
 private:
diff --git a/rtcp.c b/rtcp.c
index c815287..a89640c 100644
--- a/rtcp.c
+++ b/rtcp.c
@@ -92,6 +92,16 @@ void cSatipRtcp::Process(void)
      }
 }
 
+void cSatipRtcp::Process(unsigned char *dataP, int lengthP)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (dataP && lengthP > 0) {
+     int offset = GetApplicationOffset(&lengthP);
+     if (offset >= 0)
+        tunerM.ProcessApplicationData(dataP + offset, lengthP);
+     }
+}
+
 cString cSatipRtcp::ToString(void) const
 {
   return cString::sprintf("RTCP [device %d]", tunerM.GetId());
diff --git a/rtcp.h b/rtcp.h
index 59fb176..898b310 100644
--- a/rtcp.h
+++ b/rtcp.h
@@ -30,6 +30,7 @@ public:
 public:
   virtual int GetFd(void);
   virtual void Process(void);
+  virtual void Process(unsigned char *dataP, int lengthP);
   virtual cString ToString(void) const;
 };
 
diff --git a/rtp.c b/rtp.c
index 0be033a..03ee267 100644
--- a/rtp.c
+++ b/rtp.c
@@ -142,6 +142,22 @@ void cSatipRtp::Process(void)
      }
 }
 
+void cSatipRtp::Process(unsigned char *dataP, int lengthP)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (dataP && lengthP > 0) {
+     uint64_t elapsed;
+     cTimeMs processing(0);
+     int headerlen = GetHeaderLength(dataP, lengthP);
+     if ((headerlen >= 0) && (headerlen < lengthP))
+        tunerM.ProcessVideoData(dataP + headerlen, lengthP - headerlen);
+
+     elapsed = processing.Elapsed();
+     if (elapsed > 1)
+        debug6("%s %d read(s) took %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, lengthP, elapsed, tunerM.GetId());
+     }
+}
+
 cString cSatipRtp::ToString(void) const
 {
   return cString::sprintf("RTP [device %d]", tunerM.GetId());
diff --git a/rtp.h b/rtp.h
index 104a49c..56071f9 100644
--- a/rtp.h
+++ b/rtp.h
@@ -36,6 +36,7 @@ public:
 public:
   virtual int GetFd(void);
   virtual void Process(void);
+  virtual void Process(unsigned char *dataP, int lengthP);
   virtual cString ToString(void) const;
 };
 
diff --git a/rtsp.c b/rtsp.c
index 562626e..87c194a 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -17,12 +17,14 @@ cSatipRtsp::cSatipRtsp(cSatipTunerIf &tunerP)
 : tunerM(tunerP),
   headerBufferM(),
   dataBufferM(),
-  modeM(cmUnicast),
   handleM(NULL),
   headerListM(NULL),
   errorNoMoreM(""),
   errorOutOfRangeM(""),
-  errorCheckSyntaxM("")
+  errorCheckSyntaxM(""),
+  modeM(cSatipConfig::eTransportModeUnicast),
+  interleavedRtpIdM(0),
+  interleavedRtcpIdM(1)
 {
   debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
   Create();
@@ -58,6 +60,30 @@ size_t cSatipRtsp::DataCallback(char *ptrP, size_t sizeP, size_t nmembP, void *d
   return len;
 }
 
+size_t cSatipRtsp::InterleaveCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP)
+{
+  cSatipRtsp *obj = reinterpret_cast<cSatipRtsp *>(dataP);
+  size_t len = sizeP * nmembP;
+  debug16("%s len=%zu", __PRETTY_FUNCTION__, len);
+
+  if (obj && ptrP && len > 0) {
+     char tag = ptrP[0] & 0xFF;
+     if (tag == '$') {
+        int count = ((ptrP[2] & 0xFF) << 8) | (ptrP[3] & 0xFF);
+        if (count > 0) {
+           unsigned int channel = ptrP[1] & 0xFF;
+           u_char *data = (u_char *)&ptrP[4];
+           if (channel == obj->interleavedRtpIdM)
+              obj->tunerM.ProcessRtpData(data, count);
+           else if (channel == obj->interleavedRtcpIdM)
+              obj->tunerM.ProcessRtcpData(data, count);
+           }
+        }
+     }
+
+  return len;
+}
+
 int cSatipRtsp::DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP)
 {
   cSatipRtsp *obj = reinterpret_cast<cSatipRtsp *>(userPtrP);
@@ -87,6 +113,21 @@ int cSatipRtsp::DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, s
   return 0;
 }
 
+cString cSatipRtsp::GetActiveMode(void)
+{
+  switch (modeM) {
+    case cSatipConfig::eTransportModeUnicast:
+         return "Unicast";
+    case cSatipConfig::eTransportModeMulticast:
+         return "Multicast";
+    case cSatipConfig::eTransportModeRtpOverTcp:
+         return "RTP-over-TCP";
+    default:
+         break;
+    }
+  return "";
+}
+
 cString cSatipRtsp::RtspUnescapeString(const char *strP)
 {
   debug1("%s (%s) [device %d]", __PRETTY_FUNCTION__, strP, tunerM.GetId());
@@ -123,6 +164,9 @@ void cSatipRtsp::Create(void)
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_TIMEOUT_MS, (long)eConnectTimeoutMs);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_CONNECTTIMEOUT_MS, (long)eConnectTimeoutMs);
 
+     // Limit download speed (bytes/s)
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_MAX_RECV_SPEED_LARGE, eMaxDownloadSpeedMBits * 131072L);
+
      // Set user-agent
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_USERAGENT, *cString::sprintf("vdr-%s/%s (device %d)", PLUGIN_NAME_I18N, VERSION, tunerM.GetId()));
      }
@@ -171,9 +215,9 @@ bool cSatipRtsp::Options(const char *uriP)
   return result;
 }
 
-bool cSatipRtsp::Setup(const char *uriP, int rtpPortP, int rtcpPortP)
+bool cSatipRtsp::Setup(const char *uriP, int rtpPortP, int rtcpPortP, bool useTcpP)
 {
-  debug1("%s (%s, %d, %d) [device %d]", __PRETTY_FUNCTION__, uriP, rtpPortP, rtcpPortP, tunerM.GetId());
+  debug1("%s (%s, %d, %d, %d) [device %d]", __PRETTY_FUNCTION__, uriP, rtpPortP, rtcpPortP, useTcpP, tunerM.GetId());
   bool result = false;
 
   if (handleM && !isempty(uriP)) {
@@ -182,15 +226,18 @@ bool cSatipRtsp::Setup(const char *uriP, int rtpPortP, int rtcpPortP)
      cTimeMs processing(0);
      CURLcode res = CURLE_OK;
 
-     switch (modeM) {
-       case cmMulticast:
-            // RTP/AVP;multicast;destination=<IP multicast address>;port=<RTP port>-<RTCP port>;ttl=<ttl>
-            transport = cString::sprintf("RTP/AVP;multicast;port=%d-%d", rtpPortP, rtcpPortP);
+     switch (SatipConfig.GetTransportMode()) {
+       case cSatipConfig::eTransportModeMulticast:
+            // RTP/AVP;multicast;destination=<multicast group address>;port=<RTP port>-<RTCP port>;ttl=<ttl>[;source=<multicast source address>]
+            transport = cString::sprintf("RTP/AVP;multicast");
             break;
        default:
-       case cmUnicast:
             // RTP/AVP;unicast;client_port=<client RTP port>-<client RTCP port>
-            transport = cString::sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPortP, rtcpPortP);
+            // RTP/AVP/TCP;unicast;client_port=<client RTP port>-<client RTCP port>
+            if (useTcpP)
+               transport = cString::sprintf("RTP/AVP/TCP;unicast;interleaved=%u-%u", interleavedRtpIdM, interleavedRtcpIdM);
+            else
+               transport = cString::sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPortP, rtcpPortP);
             break;
        }
 
@@ -203,6 +250,9 @@ bool cSatipRtsp::Setup(const char *uriP, int rtpPortP, int rtcpPortP)
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, this);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, cSatipRtsp::DataCallback);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, this);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEFUNCTION, NULL);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEDATA, NULL);
+
      SATIP_CURL_EASY_PERFORM(handleM);
      // Session id is now known - disable header parsing
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, NULL);
@@ -310,6 +360,8 @@ bool cSatipRtsp::Teardown(const char *uriP)
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_TEARDOWN);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, cSatipRtsp::DataCallback);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, this);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEFUNCTION, NULL);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEDATA, NULL);
      SATIP_CURL_EASY_PERFORM(handleM);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, NULL);
      SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, NULL);
@@ -351,6 +403,35 @@ void cSatipRtsp::ParseHeader(void)
               tunerM.SetSessionTimeout(skipspace(session), -1);
            FREE_POINTER(session);
            }
+        else if (strstr(r, "Transport:")) {
+           CURLcode res = CURLE_OK;
+           int rtp = -1, rtcp = -1, ttl = -1;
+           char *tmp = NULL, *destination = NULL, *source = NULL;
+           interleavedRtpIdM = 0;
+           interleavedRtcpIdM = 1;
+           SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEFUNCTION, NULL);
+           SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEDATA, NULL);
+           if (sscanf(r, "Transport:%m[^;];unicast;client_port=%11d-%11d", &tmp, &rtp, &rtcp) == 3) {
+              modeM = cSatipConfig::eTransportModeUnicast;
+              tunerM.SetupTransport(rtp, rtcp, NULL, NULL);
+              }
+           else if (sscanf(r, "Transport:%m[^;];multicast;destination=%m[^;];port=%11d-%11d;ttl=%11d;source=%m[^;]", &tmp, &destination, &rtp, &rtcp, &ttl, &source) == 6 ||
+                    sscanf(r, "Transport:%m[^;];multicast;destination=%m[^;];port=%11d-%11d;ttl=%11d", &tmp, &destination, &rtp, &rtcp, &ttl) == 5) {
+              modeM = cSatipConfig::eTransportModeMulticast;
+              tunerM.SetupTransport(rtp, rtcp, destination, source);
+              }
+           else if (sscanf(r, "Transport:%m[^;];interleaved=%11d-%11d", &tmp, &rtp, &rtcp) == 3) {
+              interleavedRtpIdM = rtp;
+              interleavedRtcpIdM = rtcp;
+              SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEFUNCTION, cSatipRtsp::InterleaveCallback);
+              SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_INTERLEAVEDATA, this);
+              modeM = cSatipConfig::eTransportModeRtpOverTcp;
+              tunerM.SetupTransport(-1, -1, NULL, NULL);
+              }
+           FREE_POINTER(tmp);
+           FREE_POINTER(destination);
+           FREE_POINTER(source);
+           }
         r = strtok_r(NULL, "\r\n", &s);
         }
 }
@@ -418,10 +499,12 @@ bool cSatipRtsp::ValidateLatestResponse(long *rcP)
             // SETUP PLAY TEARDOWN
             // The message body of the response may contain the "Out-of-Range:" parameter followed
             // by a space-separated list of the attribute names that are not understood:
-            // "src" "fe" "freq" "pol" "msys" "mtype" "plts" "ro" "sr" "fec" "pids" "addpids" "delpids" "mcast
+            // "src" "fe" "freq" "pol" "msys" "mtype" "plts" "ro" "sr" "fec" "pids" "addpids" "delpids" "mcast"
             if (!isempty(*errorOutOfRangeM)) {
                SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_EFFECTIVE_URL, &url);
                error("Out of range: %s (error code %ld: %s) [device %d]", *errorOutOfRangeM, rc, url, tunerM.GetId());
+               // Reseting the connection wouldn't help anything due to invalid channel configuration, so let it be successful
+               result = true;
                break;
                }
        case 503:
diff --git a/rtsp.h b/rtsp.h
index 1897ac6..fb09c27 100644
--- a/rtsp.h
+++ b/rtsp.h
@@ -22,22 +22,25 @@ class cSatipRtsp {
 private:
   static size_t HeaderCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP);
   static size_t DataCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP);
+  static size_t InterleaveCallback(char *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
+    eConnectTimeoutMs      = 1500,  // in milliseconds
+    eMaxDownloadSpeedMBits = 20,    // in megabits per second
   };
-  enum eCommunicationMode { cmUnicast, cmMulticast };
 
   cSatipTunerIf &tunerM;
   cSatipMemoryBuffer headerBufferM;
   cSatipMemoryBuffer dataBufferM;
-  eCommunicationMode modeM;
   CURL *handleM;
   struct curl_slist *headerListM;
   cString errorNoMoreM;
   cString errorOutOfRangeM;
   cString errorCheckSyntaxM;
+  int modeM;
+  unsigned int interleavedRtpIdM;
+  unsigned int interleavedRtcpIdM;
 
   void Create(void);
   void Destroy(void);
@@ -53,10 +56,11 @@ public:
   explicit cSatipRtsp(cSatipTunerIf &tunerP);
   virtual ~cSatipRtsp();
 
+  cString GetActiveMode(void);
   cString RtspUnescapeString(const char *strP);
   void Reset(void);
   bool Options(const char *uriP);
-  bool Setup(const char *uriP, int rtpPortP, int rtcpPortP);
+  bool Setup(const char *uriP, int rtpPortP, int rtcpPortP, bool useTcpP);
   bool SetSession(const char *sessionP);
   bool Describe(const char *uriP);
   bool Play(const char *uriP);
diff --git a/satip.c b/satip.c
index 3453c48..089c3e2 100644
--- a/satip.c
+++ b/satip.c
@@ -27,7 +27,7 @@
 #define GITVERSION ""
 #endif
 
-       const char VERSION[]     = "2.2.3" GITVERSION;
+       const char VERSION[]     = "2.2.4" GITVERSION;
 static const char DESCRIPTION[] = trNOOP("SAT>IP Devices");
 
 class cPluginSatip : public cPlugin {
@@ -35,6 +35,7 @@ private:
   unsigned int deviceCountM;
   cSatipDiscoverServers *serversM;
   void ParseServer(const char *paramP);
+  void ParsePortRange(const char *paramP);
   int ParseCicams(const char *valueP, int *cicamsP);
   int ParseSources(const char *valueP, int *sourcesP);
   int ParseFilters(const char *valueP, int *filtersP);
@@ -83,11 +84,13 @@ const char *cPluginSatip::CommandLineHelp(void)
   // Return a string that describes all known command line options.
   return "  -d <num>, --devices=<number>  set number of devices to be created\n"
          "  -t <mode>, --trace=<mode>     set the tracing mode\n"
-         "  -s <ipaddr>|<model>|<desc>, --server=<ipaddr1>|<model1>|<desc1>;<ipaddr2>|<model2>|<desc2>\n"
+         "  -s <ipaddr>|<model>|<desc>, --server=<ipaddr1>|<model1>|<desc1>;<ipaddr2>:<port>|<model2>:<filter>|<desc2>:<quirk>\n"
          "                                define hard-coded SAT>IP server(s)\n"
          "  -D, --detach                  set the detached mode on\n"
          "  -S, --single                  set the single model server mode on\n"
-         "  -n, --noquirks                disable all the server quirks\n";
+         "  -n, --noquirks                disable autodetection of the server quirks\n"
+         "  -p, --portrange=<start>-<end> set a range of ports used for the RT[C]P server\n"
+         "                                a minimum of 2 ports per device is required.\n";
 }
 
 bool cPluginSatip::ProcessArgs(int argc, char *argv[])
@@ -98,6 +101,7 @@ bool cPluginSatip::ProcessArgs(int argc, char *argv[])
     { "devices",  required_argument, NULL, 'd' },
     { "trace",    required_argument, NULL, 't' },
     { "server",   required_argument, NULL, 's' },
+    { "portrange",required_argument, NULL, 'p' },
     { "detach",   no_argument,       NULL, 'D' },
     { "single",   no_argument,       NULL, 'S' },
     { "noquirks", no_argument,       NULL, 'n' },
@@ -105,8 +109,9 @@ bool cPluginSatip::ProcessArgs(int argc, char *argv[])
     };
 
   cString server;
+  cString portrange;
   int c;
-  while ((c = getopt_long(argc, argv, "d:t:s:DSn", long_options, NULL)) != -1) {
+  while ((c = getopt_long(argc, argv, "d:t:s:p:DSn", long_options, NULL)) != -1) {
     switch (c) {
       case 'd':
            deviceCountM = strtol(optarg, NULL, 0);
@@ -126,10 +131,15 @@ bool cPluginSatip::ProcessArgs(int argc, char *argv[])
       case 'n':
            SatipConfig.SetDisableServerQuirks(true);
            break;
+      case 'p':
+           portrange = optarg;
+           break;
       default:
            return false;
       }
     }
+  if (!isempty(*portrange))
+     ParsePortRange(portrange);
   // this must be done after all parameters are parsed
   if (!isempty(*server))
      ParseServer(*server);
@@ -222,7 +232,9 @@ void cPluginSatip::ParseServer(const char *paramP)
   while (r) {
         r = skipspace(r);
         debug3("%s server[%d]=%s", __PRETTY_FUNCTION__, n, r);
-        cString serverAddr, serverModel, serverDescription;
+        cString serverAddr, serverModel, serverFilters, serverDescription;
+        int serverQuirk = cSatipServer::eSatipQuirkNone;
+        int serverPort = SATIP_DEFAULT_RTSP_PORT;
         int n2 = 0;
         char *s2, *p2 = r;
         char *r2 = strtok_r(p2, "|", &s2);
@@ -230,13 +242,34 @@ void cPluginSatip::ParseServer(const char *paramP)
               debug3("%s param[%d]=%s", __PRETTY_FUNCTION__, n2, r2);
               switch (n2++) {
                      case 0:
+                          {
                           serverAddr = r2;
+                          char *r3 = strchr(r2, ':');
+                          if (r3) {
+                             serverPort = strtol(r3 + 1, NULL, 0);
+                             serverAddr = serverAddr.Truncate(r3 - r2);
+                             }
+                          }
                           break;
                      case 1:
+                          {
                           serverModel = r2;
+                          char *r3 = strchr(r2, ':');
+                          if (r3) {
+                             serverFilters = r3 + 1;
+                             serverModel = serverModel.Truncate(r3 - r2);
+                             }
+                          }
                           break;
                      case 2:
+                          {
                           serverDescription = r2;
+                          char *r3 = strchr(r2, ':');
+                          if (r3) {
+                             serverQuirk = strtol(r3 + 1, NULL, 0);
+                             serverDescription = serverDescription.Truncate(r3 - r2);
+                             }
+                          }
                           break;
                      default:
                           break;
@@ -244,10 +277,10 @@ void cPluginSatip::ParseServer(const char *paramP)
               r2 = strtok_r(NULL, "|", &s2);
               }
         if (*serverAddr && *serverModel && *serverDescription) {
-           debug1("%s ipaddr=%s model=%s desc=%s", __PRETTY_FUNCTION__, *serverAddr, *serverModel, *serverDescription);
+           debug1("%s ipaddr=%s port=%d model=%s (%s) desc=%s (%d)", __PRETTY_FUNCTION__, *serverAddr, serverPort, *serverModel, *serverFilters, *serverDescription, serverQuirk);
            if (!serversM)
               serversM = new cSatipDiscoverServers();
-           serversM->Add(new cSatipDiscoverServer(*serverAddr, *serverModel, *serverDescription));
+           serversM->Add(new cSatipDiscoverServer(*serverAddr, serverPort, *serverModel, *serverFilters, *serverDescription, serverQuirk));
            }
         ++n;
         r = strtok_r(NULL, ";", &s);
@@ -255,6 +288,37 @@ void cPluginSatip::ParseServer(const char *paramP)
   FREE_POINTER(p);
 }
 
+void cPluginSatip::ParsePortRange(const char *paramP)
+{
+  char *s, *p = skipspace(paramP);
+  char *r = strtok_r(p, "-", &s);
+  unsigned int rangeStart = 0;
+  unsigned int rangeStop = 0;
+  if (r) {
+     rangeStart = strtol(r, NULL, 0);
+     r = strtok_r(NULL, "-", &s);
+     }
+  if (r)
+     rangeStop = strtol(r, NULL, 0);
+  else {
+     error("Port range argument not valid '%s'", paramP);
+     rangeStart = 0;
+     rangeStop = 0;
+     }
+  if (rangeStart % 2) {
+     error("The given range start port must be even!");
+     rangeStart = 0;
+     rangeStop = 0;
+     }
+  else if (rangeStop - rangeStart + 1 < deviceCountM * 2) {
+     error("The given port range is to small: %d < %d!", rangeStop - rangeStart + 1, deviceCountM * 2);
+     rangeStart = 0;
+     rangeStop = 0;
+     }
+  SatipConfig.SetPortRangeStart(rangeStart);
+  SatipConfig.SetPortRangeStop(rangeStop);
+}
+
 int cPluginSatip::ParseCicams(const char *valueP, int *cicamsP)
 {
   debug1("%s (%s,)", __PRETTY_FUNCTION__, valueP);
@@ -342,6 +406,8 @@ bool cPluginSatip::SetupParse(const char *nameP, const char *valueP)
      for (unsigned int i = 0; i < DisabledFiltersCount; ++i)
          SatipConfig.SetDisabledFilters(i, DisabledFilters[i]);
      }
+  else if (!strcasecmp(nameP, "TransportMode"))
+     SatipConfig.SetTransportMode(atoi(valueP));
   else
      return false;
   return true;
diff --git a/sectionfilter.c b/sectionfilter.c
index edf0382..10f655b 100644
--- a/sectionfilter.c
+++ b/sectionfilter.c
@@ -338,6 +338,19 @@ cString cSatipSectionFilterHandler::GetInformation(void)
   return s;
 }
 
+bool cSatipSectionFilterHandler::Exists(u_short pidP)
+{
+  debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, pidP, deviceIndexM);
+  cMutexLock MutexLock(&mutexM);
+  for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
+      if (filtersM[i] && (pidP == filtersM[i]->GetPid())) {
+         debug12("%s (%d) Found [device %d]", __PRETTY_FUNCTION__, pidP, deviceIndexM);
+         return true;
+         }
+      }
+  return false;
+}
+
 bool cSatipSectionFilterHandler::Delete(unsigned int indexP)
 {
   debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, indexP, deviceIndexM);
diff --git a/sectionfilter.h b/sectionfilter.h
index e25c897..833511c 100644
--- a/sectionfilter.h
+++ b/sectionfilter.h
@@ -80,6 +80,7 @@ public:
   cSatipSectionFilterHandler(int deviceIndexP, unsigned int bufferLenP);
   virtual ~cSatipSectionFilterHandler();
   cString GetInformation(void);
+  bool Exists(u_short pidP);
   int Open(u_short pidP, u_char tidP, u_char maskP);
   void Close(int handleP);
   int GetPid(int handleP);
diff --git a/server.c b/server.c
index b732b3b..01613a4 100644
--- a/server.c
+++ b/server.c
@@ -80,16 +80,38 @@ bool cSatipFrontends::Detach(int deviceIdP, int transponderP)
 
 // --- cSatipServer -----------------------------------------------------------
 
-cSatipServer::cSatipServer(const char *addressP, const char *modelP, const char *descriptionP)
+cSatipServer::cSatipServer(const char *addressP, const int portP, const char *modelP, const char *filtersP, const char *descriptionP, const int quirkP)
 : addressM((addressP && *addressP) ? addressP : "0.0.0.0"),
   modelM((modelP && *modelP) ? modelP : "DVBS-1"),
+  filtersM((filtersP && *filtersP) ? filtersP : ""),
   descriptionM(!isempty(descriptionP) ? descriptionP : "MyBrokenHardware"),
   quirksM(""),
-  quirkM(eSatipQuirkNone),
+  portM(portP),
+  quirkM(quirkP),
   hasCiM(false),
+  activeM(true),
   createdM(time(NULL)),
   lastSeenM(0)
 {
+  memset(sourceFiltersM, 0, sizeof(sourceFiltersM));
+  if (!isempty(*filtersM)) {
+     char *s, *p = strdup(*filtersM);
+     char *r = strtok_r(p, ",", &s);
+     unsigned int i = 0;
+     while (r) {
+           int t = cSource::FromString(skipspace(r));
+           if (t && i < ELEMENTS(sourceFiltersM))
+              sourceFiltersM[i++] = t;
+           r = strtok_r(NULL, ",", &s);
+           }
+     if (i) {
+        filtersM = "";
+        for (unsigned int j = 0; j < i; ++j)
+            filtersM = cString::sprintf("%s%s%s", *filtersM, isempty(*filtersM) ? "" : ",", *cSource::ToString(sourceFiltersM[j]));
+        debug3("%s filters=%s", __PRETTY_FUNCTION__, *filtersM);
+        }
+     FREE_POINTER(p);
+     }
   if (!SatipConfig.GetDisableServerQuirks()) {
      // These devices contain a session id bug:
      // Inverto Airscreen Server IDL 400 ?
@@ -97,28 +119,61 @@ cSatipServer::cSatipServer(const char *addressP, const char *modelP, const char
      if (strstr(*descriptionM, "GSSBOX") ||             // Grundig Sat Systems GSS.box DSI 400
          strstr(*descriptionM, "DIGIBIT") ||            // Telestar Digibit R1
          strstr(*descriptionM, "Triax SatIP Converter") // Triax TSS 400
-        ) {
+        )
         quirkM |= eSatipQuirkSessionId;
-        quirksM = cString::sprintf("%s%sSessionId", *quirksM, isempty(*quirksM) ? "" : ",");
-        }
+     // These devices contain support for RTP over TCP:
+     if (strstr(*descriptionM, "minisatip") ||          // minisatip server
+         strstr(*descriptionM, "DVBViewer") ||          // DVBViewer Media Server
+         strstr(*descriptionM, "GSSBOX") ||             // Grundig Sat Systems GSS.box DSI 400
+         strstr(*descriptionM, "DIGIBIT") ||            // Telestar Digibit R1
+         strstr(*descriptionM, "Triax SatIP Converter") // Triax TSS 400
+        )
+        quirkM |= eSatipQuirkRtpOverTcp;
      // These devices contain a play (add/delpids) parameter bug:
      if (strstr(*descriptionM, "fritzdvbc")             // Fritz!WLAN Repeater DVB-C
-        ) {
+        )
         quirkM |= eSatipQuirkPlayPids;
-        quirksM = cString::sprintf("%s%sPlayPids", *quirksM, isempty(*quirksM) ? "" : ",");
-        }
      // These devices contain a frontend locking bug:
      if (strstr(*descriptionM, "fritzdvbc") ||            // Fritz!WLAN Repeater DVB-C
          strstr(*descriptionM, "Schwaiger Sat>IP Server") // Schwaiger MS41IP
-        ) {
+        )
         quirkM |= eSatipQuirkForceLock;
-        quirksM = cString::sprintf("%s%sForceLock", *quirksM, isempty(*quirksM) ? "" : ",");
-        }
-     debug3("%s description=%s quirks=%s", __PRETTY_FUNCTION__, *descriptionM, *quirksM);
+     // These devices support the X_PMT protocol extension
+     if (strstr(*descriptionM, "OctopusNet") ||         // Digital Devices OctopusNet
+         strstr(*descriptionM, "minisatip")             // minisatip server
+        )
+        quirkM |= eSatipQuirkCiXpmt;
+     // These devices support the TNR protocol extension
+     if (strstr(*descriptionM, "DVBViewer")             // DVBViewer Media Server
+        )
+        quirkM |= eSatipQuirkCiTnr;
+     // These devices don't support auto-detection of pilot tones
+     if (strstr(*descriptionM, "GSSBOX") ||             // Grundig Sat Systems GSS.box DSI 400
+         strstr(*descriptionM, "DIGIBIT") ||            // Telestar Digibit R1
+         strstr(*descriptionM, "Triax SatIP Converter") // Triax TSS 400
+                                                        // Kathrein ExIP 414/E
+        )
+        quirkM |= eSatipQuirkForcePilot;
      }
-  // These devices support the X_PMT protocol extension
+  if ((quirkM & eSatipQuirkMask) & eSatipQuirkSessionId)
+     quirksM = cString::sprintf("%s%sSessionId", *quirksM, isempty(*quirksM) ? "" : ",");
+  if ((quirkM & eSatipQuirkMask) & eSatipQuirkPlayPids)
+     quirksM = cString::sprintf("%s%sPlayPids", *quirksM, isempty(*quirksM) ? "" : ",");
+  if ((quirkM & eSatipQuirkMask) & eSatipQuirkForceLock)
+     quirksM = cString::sprintf("%s%sForceLock", *quirksM, isempty(*quirksM) ? "" : ",");
+  if ((quirkM & eSatipQuirkMask) & eSatipQuirkRtpOverTcp)
+     quirksM = cString::sprintf("%s%sRtpOverTcp", *quirksM, isempty(*quirksM) ? "" : ",");
+  if ((quirkM & eSatipQuirkMask) & eSatipQuirkCiXpmt)
+     quirksM = cString::sprintf("%s%sCiXpmt", *quirksM, isempty(*quirksM) ? "" : ",");
+  if ((quirkM & eSatipQuirkMask) & eSatipQuirkCiTnr)
+     quirksM = cString::sprintf("%s%sCiTnr", *quirksM, isempty(*quirksM) ? "" : ",");
+  if ((quirkM & eSatipQuirkMask) & eSatipQuirkForcePilot)
+     quirksM = cString::sprintf("%s%sForcePilot", *quirksM, isempty(*quirksM) ? "" : ",");
+  debug3("%s description=%s quirks=%s", __PRETTY_FUNCTION__, *descriptionM, *quirksM);
+  // These devices support external CI
   if (strstr(*descriptionM, "OctopusNet") ||            // Digital Devices OctopusNet
-      strstr(*descriptionM, "minisatip")                // minisatip server
+      strstr(*descriptionM, "minisatip") ||             // minisatip server
+      strstr(*descriptionM, "DVBViewer")                // DVBViewer Media Server
      ) {
      hasCiM = true;
      }
@@ -153,7 +208,7 @@ cSatipServer::cSatipServer(const char *addressP, const char *modelP, const char
            }
         r = strtok_r(NULL, ",", &s);
         }
-  free(p);
+  FREE_POINTER(p);
 }
 
 cSatipServer::~cSatipServer()
@@ -172,53 +227,72 @@ int cSatipServer::Compare(const cListObject &listObjectP) const
   return result;
 }
 
+bool cSatipServer::IsValidSource(int sourceP)
+{
+  if (sourceFiltersM[0]) {
+     for (unsigned int i = 0; i < ELEMENTS(sourceFiltersM); ++i) {
+         if (sourceP == sourceFiltersM[i]) {
+            return true;
+            }
+         }
+     return false;
+     }
+  return true;
+}
+
 bool cSatipServer::Assign(int deviceIdP, int sourceP, int systemP, int transponderP)
 {
   bool result = false;
-  if (cSource::IsType(sourceP, 'S'))
-     result = frontendsM[eSatipFrontendDVBS2].Assign(deviceIdP, transponderP);
-  else if (cSource::IsType(sourceP, 'T')) {
-     if (systemP)
-        result = frontendsM[eSatipFrontendDVBT2].Assign(deviceIdP, transponderP);
-     else
-        result = frontendsM[eSatipFrontendDVBT].Assign(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBT2].Assign(deviceIdP, transponderP);
-     }
-  else if (cSource::IsType(sourceP, 'C')) {
-     if (systemP)
-        result = frontendsM[eSatipFrontendDVBC2].Assign(deviceIdP, transponderP);
-     else
-        result = frontendsM[eSatipFrontendDVBC].Assign(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBC2].Assign(deviceIdP, transponderP);
+  if (IsValidSource(sourceP)) {
+     if (cSource::IsType(sourceP, 'S'))
+        result = frontendsM[eSatipFrontendDVBS2].Assign(deviceIdP, transponderP);
+     else if (cSource::IsType(sourceP, 'T')) {
+        if (systemP)
+           result = frontendsM[eSatipFrontendDVBT2].Assign(deviceIdP, transponderP);
+        else
+           result = frontendsM[eSatipFrontendDVBT].Assign(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBT2].Assign(deviceIdP, transponderP);
+        }
+     else if (cSource::IsType(sourceP, 'C')) {
+        if (systemP)
+           result = frontendsM[eSatipFrontendDVBC2].Assign(deviceIdP, transponderP);
+        else
+           result = frontendsM[eSatipFrontendDVBC].Assign(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBC2].Assign(deviceIdP, transponderP);
+        }
      }
   return result;
 }
 
 bool cSatipServer::Matches(int sourceP)
 {
-  if (cSource::IsType(sourceP, 'S'))
-     return GetModulesDVBS2();
-  else if (cSource::IsType(sourceP, 'T'))
-     return GetModulesDVBT() || GetModulesDVBT2();
-  else if (cSource::IsType(sourceP, 'C'))
-     return GetModulesDVBC() || GetModulesDVBC2();
+  if (IsValidSource(sourceP)) {
+     if (cSource::IsType(sourceP, 'S'))
+        return GetModulesDVBS2();
+     else if (cSource::IsType(sourceP, 'T'))
+        return GetModulesDVBT() || GetModulesDVBT2();
+     else if (cSource::IsType(sourceP, 'C'))
+        return GetModulesDVBC() || GetModulesDVBC2();
+     }
   return false;
 }
 
 bool cSatipServer::Matches(int deviceIdP, int sourceP, int systemP, int transponderP)
 {
   bool result = false;
-  if (cSource::IsType(sourceP, 'S'))
-     result = frontendsM[eSatipFrontendDVBS2].Matches(deviceIdP, transponderP);
-  else if (cSource::IsType(sourceP, 'T')) {
-     if (systemP)
-        result = frontendsM[eSatipFrontendDVBT2].Matches(deviceIdP, transponderP);
-     else
-        result = frontendsM[eSatipFrontendDVBT].Matches(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBT2].Matches(deviceIdP, transponderP);
-     }
-  else if (cSource::IsType(sourceP, 'C')) {
-     if (systemP)
-        result = frontendsM[eSatipFrontendDVBC2].Matches(deviceIdP, transponderP);
-     else
-        result = frontendsM[eSatipFrontendDVBC].Matches(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBC2].Matches(deviceIdP, transponderP);
+  if (IsValidSource(sourceP)) {
+     if (cSource::IsType(sourceP, 'S'))
+        result = frontendsM[eSatipFrontendDVBS2].Matches(deviceIdP, transponderP);
+     else if (cSource::IsType(sourceP, 'T')) {
+        if (systemP)
+           result = frontendsM[eSatipFrontendDVBT2].Matches(deviceIdP, transponderP);
+        else
+           result = frontendsM[eSatipFrontendDVBT].Matches(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBT2].Matches(deviceIdP, transponderP);
+        }
+     else if (cSource::IsType(sourceP, 'C')) {
+        if (systemP)
+           result = frontendsM[eSatipFrontendDVBC2].Matches(deviceIdP, transponderP);
+        else
+           result = frontendsM[eSatipFrontendDVBC].Matches(deviceIdP, transponderP) || frontendsM[eSatipFrontendDVBC2].Matches(deviceIdP, transponderP);
+        }
      }
   return result;
 }
@@ -287,11 +361,11 @@ cSatipServer *cSatipServers::Find(int sourceP)
 cSatipServer *cSatipServers::Assign(int deviceIdP, int sourceP, int transponderP, int systemP)
 {
   for (cSatipServer *s = First(); s; s = Next(s)) {
-      if (s->Matches(deviceIdP, sourceP, systemP, transponderP))
+      if (s->IsActive() && s->Matches(deviceIdP, sourceP, systemP, transponderP))
          return s;
       }
   for (cSatipServer *s = First(); s; s = Next(s)) {
-      if (s->Assign(deviceIdP, sourceP, systemP, transponderP))
+      if (s->IsActive() && s->Assign(deviceIdP, sourceP, systemP, transponderP))
          return s;
       }
   return NULL;
@@ -308,6 +382,16 @@ cSatipServer *cSatipServers::Update(cSatipServer *serverP)
   return NULL;
 }
 
+void cSatipServers::Activate(cSatipServer *serverP, bool onOffP)
+{
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s == serverP) {
+         s->Activate(onOffP);
+         break;
+         }
+      }
+}
+
 void cSatipServers::Attach(cSatipServer *serverP, int deviceIdP, int transponderP)
 {
   for (cSatipServer *s = First(); s; s = Next(s)) {
@@ -374,6 +458,18 @@ cString cSatipServers::GetAddress(cSatipServer *serverP)
   return address;
 }
 
+int cSatipServers::GetPort(cSatipServer *serverP)
+{
+  int port = SATIP_DEFAULT_RTSP_PORT;
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s == serverP) {
+         port = s->Port();
+         break;
+         }
+      }
+  return port;
+}
+
 cString cSatipServers::GetString(cSatipServer *serverP)
 {
   cString list = "";
@@ -390,7 +486,7 @@ cString cSatipServers::List(void)
 {
   cString list = "";
   for (cSatipServer *s = First(); s; s = Next(s))
-      list = cString::sprintf("%s%s|%s|%s\n", *list, s->Address(), s->Model(), s->Description());
+      list = cString::sprintf("%s%c %s|%s|%s\n", *list, s->IsActive() ? '+' : '-', s->Address(), s->Model(), s->Description());
   return list;
 }
 
diff --git a/server.h b/server.h
index 45bb1df..ea9ae2d 100644
--- a/server.h
+++ b/server.h
@@ -54,25 +54,37 @@ private:
     eSatipFrontendDVBC2,
     eSatipFrontendCount
   };
+  enum {
+    eSatipMaxSourceFilters = 16
+  };
   cString addressM;
   cString modelM;
+  cString filtersM;
   cString descriptionM;
   cString quirksM;
   cSatipFrontends frontendsM[eSatipFrontendCount];
+  int sourceFiltersM[eSatipMaxSourceFilters];
+  int portM;
   int quirkM;
   bool hasCiM;
+  bool activeM;
   time_t createdM;
   cTimeMs lastSeenM;
+  bool IsValidSource(int sourceP);
 
 public:
   enum eSatipQuirk {
-    eSatipQuirkNone      = 0x00,
-    eSatipQuirkSessionId = 0x01,
-    eSatipQuirkPlayPids  = 0x02,
-    eSatipQuirkForceLock = 0x04,
-    eSatipQuirkMask      = 0x0F
+    eSatipQuirkNone       = 0x00,
+    eSatipQuirkSessionId  = 0x01,
+    eSatipQuirkPlayPids   = 0x02,
+    eSatipQuirkForceLock  = 0x04,
+    eSatipQuirkRtpOverTcp = 0x08,
+    eSatipQuirkCiXpmt     = 0x10,
+    eSatipQuirkCiTnr      = 0x20,
+    eSatipQuirkForcePilot = 0x40,
+    eSatipQuirkMask       = 0xFF
   };
-  cSatipServer(const char *addressP, const char *modelP, const char *descriptionP);
+  cSatipServer(const char *addressP, const int portP, const char *modelP, const char *filtersP, const char *descriptionP, const int quirkP);
   virtual ~cSatipServer();
   virtual int Compare(const cListObject &listObjectP) const;
   bool Assign(int deviceIdP, int sourceP, int systemP, int transponderP);
@@ -85,13 +97,17 @@ public:
   int GetModulesDVBT2(void);
   int GetModulesDVBC(void);
   int GetModulesDVBC2(void);
+  void Activate(bool onOffP)    { activeM = onOffP; }
   const char *Address(void)     { return *addressM; }
   const char *Model(void)       { return *modelM; }
+  const char *Filters(void)     { return *filtersM; }
   const char *Description(void) { return *descriptionM; }
   const char *Quirks(void)      { return *quirksM; }
+  int Port(void)                { return portM; }
   bool Quirk(int quirkP)        { return ((quirkP & eSatipQuirkMask) & quirkM); }
   bool HasQuirk(void)           { return (quirkM != eSatipQuirkNone); }
   bool HasCI(void)              { return hasCiM; }
+  bool IsActive(void)           { return activeM; }
   void Update(void)             { lastSeenM.Set(); }
   uint64_t LastSeen(void)       { return lastSeenM.Elapsed(); }
   time_t Created(void)          { return createdM; }
@@ -105,6 +121,7 @@ public:
   cSatipServer *Find(int sourceP);
   cSatipServer *Assign(int deviceIdP, int sourceP, int transponderP, int systemP);
   cSatipServer *Update(cSatipServer *serverP);
+  void Activate(cSatipServer *serverP, bool onOffP);
   void Attach(cSatipServer *serverP, int deviceIdP, int transponderP);
   void Detach(cSatipServer *serverP, int deviceIdP, int transponderP);
   bool IsQuirk(cSatipServer *serverP, int quirkP);
@@ -112,6 +129,7 @@ public:
   void Cleanup(uint64_t intervalMsP = 0);
   cString GetAddress(cSatipServer *serverP);
   cString GetString(cSatipServer *serverP);
+  int GetPort(cSatipServer *serverP);
   cString List(void);
   int NumProvidedSystems(void);
 };
diff --git a/setup.c b/setup.c
index 479ea97..c178c61 100644
--- a/setup.c
+++ b/setup.c
@@ -86,6 +86,8 @@ eOSState cSatipEditSrcItem::ProcessKey(eKeys Key)
 class cSatipServerInfo : public cOsdMenu
 {
 private:
+  cSatipServer *serverM;
+  int activeM;
   cString addressM;
   cString modelM;
   cString descriptionM;
@@ -101,6 +103,8 @@ public:
 
 cSatipServerInfo::cSatipServerInfo(cSatipServer *serverP)
 : cOsdMenu(tr("SAT>IP Server"), 20),
+  serverM(serverP),
+  activeM(serverP && serverP->IsActive()),
   addressM(serverP ? serverP->Address() : "---"),
   modelM(serverP ? serverP->Model() : "---"),
   descriptionM(serverP ? serverP->Description() : "---"),
@@ -118,6 +122,7 @@ cSatipServerInfo::~cSatipServerInfo()
 
 void cSatipServerInfo::Setup(void)
 {
+  Add(new cMenuEditBoolItem(trVDR("Active"), &activeM));
   Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Address"),       *addressM),              osUnknown, false));
   Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Model"),         *modelM),                osUnknown, false));
   Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Description"),   *descriptionM),          osUnknown, false));
@@ -127,6 +132,7 @@ void cSatipServerInfo::Setup(void)
 
 eOSState cSatipServerInfo::ProcessKey(eKeys keyP)
 {
+  int oldActive = activeM;
   eOSState state = cOsdMenu::ProcessKey(keyP);
 
   if (state == osUnknown) {
@@ -135,6 +141,12 @@ eOSState cSatipServerInfo::ProcessKey(eKeys keyP)
        default:  state = osContinue; break;
        }
      }
+
+  if (keyP != kNone && oldActive != activeM) {
+     cSatipDiscover::GetInstance()->ActivateServer(serverM, activeM);
+     Setup();
+     }
+
   return state;
 }
 
@@ -155,7 +167,7 @@ cSatipServerItem::cSatipServerItem(cSatipServer *serverP)
 {
   SetSelectable(true);
   // Must begin with a '#' character!
-  SetText(*cString::sprintf("# %s (%s)\t%s", serverM->Address(), serverM->Model(), serverM->Description()));
+  SetText(*cString::sprintf("%s %s (%s)\t%s", serverM->IsActive() ? "+" : "-", serverM->Address(), serverM->Model(), serverM->Description()));
 }
 
 void cSatipServerItem::SetMenuItem(cSkinDisplayMenu *displayMenuP, int indexP, bool currentP, bool selectableP)
@@ -333,6 +345,7 @@ cSatipPluginSetup::cSatipPluginSetup()
 : detachedModeM(SatipConfig.GetDetachedMode()),
   deviceCountM(0),
   operatingModeM(SatipConfig.GetOperatingMode()),
+  transportModeM(SatipConfig.GetTransportMode()),
   ciExtensionM(SatipConfig.GetCIExtension()),
   eitScanM(SatipConfig.GetEITScan()),
   numDisabledSourcesM(SatipConfig.GetDisabledSourcesCount()),
@@ -343,6 +356,9 @@ cSatipPluginSetup::cSatipPluginSetup()
   operatingModeTextsM[cSatipConfig::eOperatingModeLow]    = tr("low");
   operatingModeTextsM[cSatipConfig::eOperatingModeNormal] = tr("normal");
   operatingModeTextsM[cSatipConfig::eOperatingModeHigh]   = tr("high");
+  transportModeTextsM[cSatipConfig::eTransportModeUnicast]    = tr("Unicast");
+  transportModeTextsM[cSatipConfig::eTransportModeMulticast]  = tr("Multicast");
+  transportModeTextsM[cSatipConfig::eTransportModeRtpOverTcp] = tr("RTP-over-TCP");
   for (unsigned int i = 0; i < ELEMENTS(cicamsM); ++i)
       cicamsM[i] = SatipConfig.GetCICAM(i);
   for (unsigned int i = 0; i < ELEMENTS(ca_systems_table); ++i)
@@ -400,6 +416,8 @@ void cSatipPluginSetup::Setup(void)
          helpM.Append(tr("Define an ill-behaving filter to be blacklisted."));
          }
      }
+  Add(new cMenuEditStraItem(tr("Transport mode"), &transportModeM, ELEMENTS(transportModeTextsM), transportModeTextsM));
+  helpM.Append(tr("Define which transport mode shall be used.\n\nUnicast, Multicast, RTP-over-TCP"));
   Add(new cOsdItem(tr("Active SAT>IP servers:"), osUnknown, false));
   helpM.Append("");
 
@@ -465,10 +483,12 @@ eOSState cSatipPluginSetup::ProcessKey(eKeys keyP)
   int oldNumDisabledFilters = numDisabledFiltersM;
   eOSState state = cMenuSetupPage::ProcessKey(keyP);
 
-  // Ugly hack with hardcoded '#' character :(
+  // Ugly hack with hardcoded '+/-' characters :(
   const char *p = Get(Current())->Text();
-  if (!hadSubMenu && !HasSubMenu() && (*p == '#') && (keyP == kOk))
+  if (!hadSubMenu && !HasSubMenu() && p && (*p == '+' || *p == '-') && (keyP == kOk))
      return DeviceInfo();
+  if (hadSubMenu && !HasSubMenu())
+     Setup();
 
   if (state == osUnknown) {
      switch (keyP) {
@@ -547,6 +567,7 @@ void cSatipPluginSetup::Store(void)
 {
   // Store values into setup.conf
   SetupStore("OperatingMode", operatingModeM);
+  SetupStore("TransportMode", transportModeM);
   SetupStore("EnableCIExtension", ciExtensionM);
   SetupStore("EnableEITScan", eitScanM);
   StoreCicams("CICAM", cicamsM);
@@ -554,6 +575,7 @@ void cSatipPluginSetup::Store(void)
   StoreFilters("DisabledFilters", disabledFilterIndexesM);
   // Update global config
   SatipConfig.SetOperatingMode(operatingModeM);
+  SatipConfig.SetTransportMode(transportModeM);
   SatipConfig.SetCIExtension(ciExtensionM);
   SatipConfig.SetEITScan(eitScanM);
   for (int i = 0; i < MAX_CICAM_COUNT; ++i)
diff --git a/setup.h b/setup.h
index d8dcd66..e97ce2f 100644
--- a/setup.h
+++ b/setup.h
@@ -18,7 +18,9 @@ private:
   bool detachedModeM;
   int deviceCountM;
   int operatingModeM;
+  int transportModeM;
   const char *operatingModeTextsM[cSatipConfig::eOperatingModeCount];
+  const char *transportModeTextsM[cSatipConfig::eTransportModeCount];
   int ciExtensionM;
   int cicamsM[MAX_CICAM_COUNT];
   const char *cicamTextsM[CA_SYSTEMS_TABLE_SIZE];
diff --git a/socket.c b/socket.c
index 29c2602..4cb19f3 100644
--- a/socket.c
+++ b/socket.c
@@ -21,7 +21,11 @@
 
 cSatipSocket::cSatipSocket()
 : socketPortM(0),
-  socketDescM(-1)
+  socketDescM(-1),
+  isMulticastM(false),
+  useSsmM(false),
+  streamAddrM(htonl(INADDR_ANY)),
+  sourceAddrM(htonl(INADDR_ANY))
 {
   debug1("%s", __PRETTY_FUNCTION__);
   memset(&sockAddrM, 0, sizeof(sockAddrM));
@@ -34,10 +38,17 @@ cSatipSocket::~cSatipSocket()
   Close();
 }
 
-bool cSatipSocket::Open(const int portP)
+bool cSatipSocket::Open(const int portP, const bool reuseP)
 {
+  // If socket is there already and it is bound to a different port, it must
+  // be closed first
+  if (portP != socketPortM) {
+     debug1("%s (%d, %d) Socket tear-down", __PRETTY_FUNCTION__, portP, reuseP);
+     Close();
+     }
   // Bind to the socket if it is not active already
   if (socketDescM < 0) {
+     int yes;
      socklen_t len = sizeof(sockAddrM);
      // Create socket
      socketDescM = socket(PF_INET, SOCK_DGRAM, 0);
@@ -46,9 +57,19 @@ bool cSatipSocket::Open(const int portP)
      ERROR_IF_FUNC(fcntl(socketDescM, F_SETFL, O_NONBLOCK), "fcntl(O_NONBLOCK)",
                    Close(), return false);
      // Allow multiple sockets to use the same PORT number
-     int yes = 1;
+     yes = reuseP;
      ERROR_IF_FUNC(setsockopt(socketDescM, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0,
                    "setsockopt(SO_REUSEADDR)", Close(), return false);
+     yes = reuseP;
+#ifdef SO_REUSEPORT
+     ERROR_IF_FUNC(setsockopt(socketDescM, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)) < 0 && errno != ENOPROTOOPT,
+                   "setsockopt(SO_REUSEPORT)", Close(), return false);
+#endif
+#ifndef __FreeBSD__
+     // Allow packet information to be fetched
+     ERROR_IF_FUNC(setsockopt(socketDescM, SOL_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0,
+                   "setsockopt(IP_PKTINFO)", Close(), return false);
+#endif // __FreeBSD__
      // Bind socket
      memset(&sockAddrM, 0, sizeof(sockAddrM));
      sockAddrM.sin_family = AF_INET;
@@ -57,23 +78,41 @@ bool cSatipSocket::Open(const int portP)
      ERROR_IF_FUNC(bind(socketDescM, (struct sockaddr *)&sockAddrM, sizeof(sockAddrM)) < 0,
                    "bind()", Close(), return false);
      // Update socket port
-     ERROR_IF_FUNC(getsockname(socketDescM,(struct sockaddr*)&sockAddrM, &len) < 0,
+     ERROR_IF_FUNC(getsockname(socketDescM, (struct sockaddr*)&sockAddrM, &len) < 0,
                    "getsockname()", Close(), return false);
      socketPortM = ntohs(sockAddrM.sin_port);
+     isMulticastM = false;
      }
   debug1("%s (%d) socketPort=%d", __PRETTY_FUNCTION__, portP, socketPortM);
   return true;
 }
 
+bool cSatipSocket::OpenMulticast(const int portP, const char *streamAddrP, const char *sourceAddrP)
+{
+  debug1("%s (%d, %s, %s)", __PRETTY_FUNCTION__, portP, streamAddrP, sourceAddrP);
+  if (Open(portP)) {
+     CheckAddress(streamAddrP, &streamAddrM);
+     if (!isempty(sourceAddrP))
+        useSsmM = CheckAddress(sourceAddrP, &sourceAddrM);
+     return Join();
+     }
+  return false;
+}
+
 void cSatipSocket::Close(void)
 {
   debug1("%s sockerPort=%d", __PRETTY_FUNCTION__, socketPortM);
   // Check if socket exists
   if (socketDescM >= 0) {
+     Leave();
      close(socketDescM);
      socketDescM = -1;
      socketPortM = 0;
      memset(&sockAddrM, 0, sizeof(sockAddrM));
+     streamAddrM = htonl(INADDR_ANY);
+     sourceAddrM = htonl(INADDR_ANY);
+     isMulticastM = false;
+     useSsmM = false;
      }
 }
 
@@ -96,6 +135,96 @@ bool cSatipSocket::Flush(void)
   return false;
 }
 
+bool cSatipSocket::CheckAddress(const char *addrP, in_addr_t *inAddrP)
+{
+  if (inAddrP) {
+     // First try only the IP address
+     *inAddrP = inet_addr(addrP);
+     if (*inAddrP == htonl(INADDR_NONE)) {
+        debug1("%s (%s, ) Cannot convert to address", __PRETTY_FUNCTION__, addrP);
+        // It may be a host name, get the name
+        struct hostent *host = gethostbyname(addrP);
+        if (!host) {
+           char tmp[64];
+           error("gethostbyname() failed: %s is not valid address: %s", addrP,
+                 strerror_r(h_errno, tmp, sizeof(tmp)));
+           return false;
+           }
+        *inAddrP = inet_addr(*host->h_addr_list);
+        }
+     return true;
+     }
+  return false;
+}
+
+bool cSatipSocket::Join(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Check if socket exists
+  if (socketDescM >= 0 && !isMulticastM) {
+     // Join a new multicast group
+     if (useSsmM) {
+        // Source-specific multicast (SSM) is used
+        struct group_source_req gsr;
+        struct sockaddr_in *grp;
+        struct sockaddr_in *src;
+        gsr.gsr_interface = 0; // if_nametoindex("any") ?
+        grp = (struct sockaddr_in*)&gsr.gsr_group;
+        grp->sin_family = AF_INET;
+        grp->sin_addr.s_addr = streamAddrM;
+        grp->sin_port = 0;
+        src = (struct sockaddr_in*)&gsr.gsr_source;
+        src->sin_family = AF_INET;
+        src->sin_addr.s_addr = sourceAddrM;
+        src->sin_port = 0;
+        ERROR_IF_RET(setsockopt(socketDescM, SOL_IP, MCAST_JOIN_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_JOIN_SOURCE_GROUP)", return false);
+        }
+     else {
+        struct ip_mreq mreq;
+        mreq.imr_multiaddr.s_addr = streamAddrM;
+        mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+        ERROR_IF_RET(setsockopt(socketDescM, SOL_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_ADD_MEMBERSHIP)", return false);
+        }
+     // Update multicasting flag
+     isMulticastM = true;
+     }
+  return true;
+}
+
+bool cSatipSocket::Leave(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Check if socket exists
+  if (socketDescM >= 0 && isMulticastM) {
+     // Leave the existing multicast group
+     if (useSsmM) {
+        // Source-specific multicast (SSM) is used
+        struct group_source_req gsr;
+        struct sockaddr_in *grp;
+        struct sockaddr_in *src;
+        gsr.gsr_interface = 0; // if_nametoindex("any") ?
+        grp = (struct sockaddr_in*)&gsr.gsr_group;
+        grp->sin_family = AF_INET;
+        grp->sin_addr.s_addr = streamAddrM;
+        grp->sin_port = 0;
+        src = (struct sockaddr_in*)&gsr.gsr_source;
+        src->sin_family = AF_INET;
+        src->sin_addr.s_addr = sourceAddrM;
+        src->sin_port = 0;
+        ERROR_IF_RET(setsockopt(socketDescM, SOL_IP, MCAST_LEAVE_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_LEAVE_SOURCE_GROUP)", return false);
+        }
+     else {
+        struct ip_mreq mreq;
+        mreq.imr_multiaddr.s_addr = streamAddrM;
+        mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+        ERROR_IF_RET(setsockopt(socketDescM, SOL_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_DROP_MEMBERSHIP)", return false);
+        }
+     // Update multicasting flag
+     isMulticastM = false;
+     }
+  return true;
+}
+
 int cSatipSocket::Read(unsigned char *bufferAddrP, unsigned int bufferLenP)
 {
   debug16("%s (, %d)", __PRETTY_FUNCTION__, bufferLenP);
@@ -126,8 +255,22 @@ int cSatipSocket::Read(unsigned char *bufferAddrP, unsigned int bufferLenP)
 
     if (socketDescM && bufferAddrP && (bufferLenP > 0))
        len = (int)recvmsg(socketDescM, &msgh, MSG_DONTWAIT);
-    if (len > 0)
-       return len;
+    if (len > 0) {
+#ifndef __FreeBSD__
+       if (isMulticastM) {
+          // Process auxiliary received data and validate source address
+          for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
+              if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
+                 struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg);
+                 if ((i->ipi_addr.s_addr == streamAddrM) || (htonl(INADDR_ANY) == streamAddrM))
+                    return len;
+                 }
+              }
+          }
+       else
+#endif // __FreeBSD__
+          return len;
+       }
     } while (len > 0);
   ERROR_IF_RET(len < 0 && errno != EAGAIN && errno != EWOULDBLOCK, "recvmsg()", return -1);
   return 0;
diff --git a/socket.h b/socket.h
index f9a93d8..12d1ac4 100644
--- a/socket.h
+++ b/socket.h
@@ -15,14 +15,23 @@ private:
   int socketPortM;
   int socketDescM;
   struct sockaddr_in sockAddrM;
+  bool isMulticastM;
+  bool useSsmM;
+  in_addr_t streamAddrM;
+  in_addr_t sourceAddrM;
+  bool CheckAddress(const char *addrP, in_addr_t *inAddrP);
+  bool Join(void);
+  bool Leave(void);
 
 public:
   cSatipSocket();
   virtual ~cSatipSocket();
-  bool Open(const int portP = 0);
+  bool Open(const int portP = 0, const bool reuseP = false);
+  bool OpenMulticast(const int portP, const char *streamAddrP, const char *sourceAddrP);
   virtual void Close(void);
   int Fd(void) { return socketDescM; }
   int Port(void) { return socketPortM; }
+  bool IsMulticast(void) { return isMulticastM; }
   bool IsOpen(void) { return (socketDescM >= 0); }
   bool Flush(void);
   int Read(unsigned char *bufferAddrP, unsigned int bufferLenP);
diff --git a/tuner.c b/tuner.c
index c12a19e..0ff758f 100644
--- a/tuner.c
+++ b/tuner.c
@@ -25,6 +25,8 @@ cSatipTuner::cSatipTuner(cSatipDeviceIf &deviceP, unsigned int packetLenP)
   rtcpM(*this),
   streamAddrM(""),
   streamParamM(""),
+  tnrParamM(""),
+  streamPortM(SATIP_DEFAULT_RTSP_PORT),
   currentServerM(NULL, deviceP.GetId(), 0),
   nextServerM(NULL, deviceP.GetId(), 0),
   mutexM(),
@@ -50,12 +52,16 @@ cSatipTuner::cSatipTuner(cSatipDeviceIf &deviceP, unsigned int packetLenP)
   debug1("%s (, %d) [device %d]", __PRETTY_FUNCTION__, packetLenP, deviceIdM);
 
   // Open sockets
-  int i = 100;
+  int i = SatipConfig.GetPortRangeStart() ? SatipConfig.GetPortRangeStop() - SatipConfig.GetPortRangeStart() - 1 : 100;
+  int port = SatipConfig.GetPortRangeStart();
   while (i-- > 0) {
-        if (rtpM.Open(0) && rtcpM.Open(rtpM.Port() + 1))
+        // RTP must use an even port number
+        if (rtpM.Open(port) && (rtpM.Port() % 2 == 0) && rtcpM.Open(rtpM.Port() + 1))
            break;
         rtpM.Close();
         rtcpM.Close();
+        if (SatipConfig.GetPortRangeStart())
+           port += 2;
         }
   if ((rtpM.Port() <= 0) || (rtcpM.Port() <= 0)) {
      error("Cannot open required RTP/RTCP ports [device %d]", deviceIdM);
@@ -205,7 +211,8 @@ bool cSatipTuner::Connect(void)
   debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
 
   if (!isempty(*streamAddrM)) {
-     cString connectionUri = cString::sprintf("rtsp://%s/", *streamAddrM);
+     cString connectionUri = GetBaseUrl(*streamAddrM, streamPortM);
+     tnrParamM = "";
      // Just retune
      if (streamIdM >= 0) {
         cString uri = cString::sprintf("%sstream=%d?%s", *connectionUri, streamIdM, *streamParamM);
@@ -217,10 +224,11 @@ bool cSatipTuner::Connect(void)
         }
      else if (rtspM.Options(*connectionUri)) {
         cString uri = cString::sprintf("%s?%s", *connectionUri, *streamParamM);
+        bool useTcp = SatipConfig.IsTransportModeRtpOverTcp() && nextServerM.IsValid() && nextServerM.IsQuirk(cSatipServer::eSatipQuirkRtpOverTcp);
         // Flush any old content
         //rtpM.Flush();
         //rtcpM.Flush();
-        if (rtspM.Setup(*uri, rtpM.Port(), rtcpM.Port())) {
+        if (rtspM.Setup(*uri, rtpM.Port(), rtcpM.Port(), useTcp)) {
            keepAliveM.Set(timeoutM);
            if (nextServerM.IsValid()) {
               currentServerM = nextServerM;
@@ -244,7 +252,7 @@ bool cSatipTuner::Disconnect(void)
   debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
 
   if (!isempty(*streamAddrM) && (streamIdM >= 0)) {
-     cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM);
+     cString uri = cString::sprintf("%sstream=%d", *GetBaseUrl(*streamAddrM, streamPortM), streamIdM);
      rtspM.Teardown(*uri);
      // some devices requires a teardown for TCP connection also
      rtspM.Reset();
@@ -289,6 +297,11 @@ void cSatipTuner::ProcessVideoData(u_char *bufferP, int lengthP)
   reConnectM.Set(eConnectTimeoutMs);
 }
 
+void cSatipTuner::ProcessRtpData(u_char *bufferP, int lengthP)
+{
+  rtpM.Process(bufferP, lengthP);
+}
+
 void cSatipTuner::ProcessApplicationData(u_char *bufferP, int lengthP)
 {
   debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, lengthP, deviceIdM);
@@ -342,6 +355,11 @@ void cSatipTuner::ProcessApplicationData(u_char *bufferP, int lengthP)
   reConnectM.Set(eConnectTimeoutMs);
 }
 
+void cSatipTuner::ProcessRtcpData(u_char *bufferP, int lengthP)
+{
+  rtcpM.Process(bufferP, lengthP);
+}
+
 void cSatipTuner::SetStreamId(int streamIdP)
 {
   cMutexLock MutexLock(&mutexM);
@@ -359,6 +377,47 @@ void cSatipTuner::SetSessionTimeout(const char *sessionP, int timeoutP)
   timeoutM = (timeoutP > eMinKeepAliveIntervalMs) ? timeoutP : eMinKeepAliveIntervalMs;
 }
 
+void cSatipTuner::SetupTransport(int rtpPortP, int rtcpPortP, const char *streamAddrP, const char *sourceAddrP)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s (%d, %d, %s, %s) [device %d]", __PRETTY_FUNCTION__, rtpPortP, rtcpPortP, streamAddrP, sourceAddrP, deviceIdM);
+  bool multicast = !isempty(streamAddrP);
+  // Adapt RTP to any transport media change
+  if (multicast != rtpM.IsMulticast() || rtpPortP != rtpM.Port()) {
+     cSatipPoller::GetInstance()->Unregister(rtpM);
+     rtpM.Close();
+     if (rtpPortP >= 0) {
+        if (multicast)
+           rtpM.OpenMulticast(rtpPortP, streamAddrP, sourceAddrP);
+        else
+           rtpM.Open(rtpPortP);
+        cSatipPoller::GetInstance()->Register(rtpM);
+        }
+     }
+  // Adapt RTCP to any transport media change
+  if (multicast != rtcpM.IsMulticast() || rtcpPortP != rtcpM.Port()) {
+     cSatipPoller::GetInstance()->Unregister(rtcpM);
+     rtcpM.Close();
+     if (rtcpPortP >= 0) {
+        if (multicast)
+           rtcpM.OpenMulticast(rtpPortP, streamAddrP, sourceAddrP);
+        else
+           rtcpM.Open(rtpPortP);
+        cSatipPoller::GetInstance()->Register(rtcpM);
+        }
+     }
+}
+
+cString cSatipTuner::GetBaseUrl(const char *addressP, const int portP)
+{
+  debug16("%s (%s, %d) [device %d]", __PRETTY_FUNCTION__, addressP, portP, deviceIdM);
+
+  if (portP != SATIP_DEFAULT_RTSP_PORT)
+     return cString::sprintf("rtsp://%s:%d/", addressP, portP);
+
+  return cString::sprintf("rtsp://%s/", addressP);
+}
+
 int cSatipTuner::GetId(void)
 {
   debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
@@ -375,6 +434,10 @@ bool cSatipTuner::SetSource(cSatipServer *serverP, const int transponderP, const
         // Update stream address and parameter
         streamAddrM = rtspM.RtspUnescapeString(*nextServerM.GetAddress());
         streamParamM = rtspM.RtspUnescapeString(parameterP);
+        streamPortM = nextServerM.GetPort();
+        // Modify parameter if required
+        if (nextServerM.IsQuirk(cSatipServer::eSatipQuirkForcePilot) && strstr(parameterP, "msys=dvbs2") && !strstr(parameterP, "plts="))
+           streamParamM = rtspM.RtspUnescapeString(*cString::sprintf("%s&plts=on", parameterP));
         // Reconnect
         RequestState(tsSet, smExternal);
         }
@@ -413,7 +476,7 @@ bool cSatipTuner::UpdatePids(bool forceP)
   cMutexLock MutexLock(&mutexM);
   if (((forceP && pidsM.Size()) || (pidUpdateCacheM.TimedOut() && (addPidsM.Size() || delPidsM.Size()))) &&
       !isempty(*streamAddrM) && (streamIdM > 0)) {
-     cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM);
+     cString uri = cString::sprintf("%sstream=%d", *GetBaseUrl(*streamAddrM, streamPortM), streamIdM);
      bool useci = (SatipConfig.GetCIExtension() && currentServerM.HasCI());
      bool usedummy = currentServerM.IsQuirk(cSatipServer::eSatipQuirkPlayPids);
      if (forceP || usedummy) {
@@ -429,20 +492,30 @@ bool cSatipTuner::UpdatePids(bool forceP)
            uri = cString::sprintf("%s%sdelpids=%s", *uri, addPidsM.Size() ? "&" : "?", *delPidsM.ListPids());
         }
      if (useci) {
-        // CI extension parameters:
-        // - x_pmt : specifies the PMT of the service you want the CI to decode
-        // - x_ci  : specfies which CI slot (1..n) to use
-        //           value 0 releases the CI slot
-        //           CI slot released automatically if the stream is released,
-        //           but not when used retuning to another channel
-        int pid = deviceM->GetPmtPid();
-        if ((pid > 0) && (pid != pmtPidM)) {
-           int slot = deviceM->GetCISlot();
-           uri = cString::sprintf("%s&x_pmt=%d", *uri, pid);
-           if (slot > 0)
-              uri = cString::sprintf("%s&x_ci=%d", *uri, slot);
+        if (currentServerM.IsQuirk(cSatipServer::eSatipQuirkCiXpmt)) {
+           // CI extension parameters:
+           // - x_pmt : specifies the PMT of the service you want the CI to decode
+           // - x_ci  : specfies which CI slot (1..n) to use
+           //           value 0 releases the CI slot
+           //           CI slot released automatically if the stream is released,
+           //           but not when used retuning to another channel
+           int pid = deviceM->GetPmtPid();
+           if ((pid > 0) && (pid != pmtPidM)) {
+              int slot = deviceM->GetCISlot();
+              uri = cString::sprintf("%s&x_pmt=%d", *uri, pid);
+              if (slot > 0)
+                 uri = cString::sprintf("%s&x_ci=%d", *uri, slot);
+              }
+           pmtPidM = pid;
+           }
+        else if (currentServerM.IsQuirk(cSatipServer::eSatipQuirkCiTnr)) {
+           // CI extension parameters:
+           // - tnr : specifies a channel config entry
+           cString param = deviceM->GetTnrParameterString();
+           if (!isempty(*param) && strcmp(*tnrParamM, *param) != 0)
+              uri = cString::sprintf("%s&tnr=%s", *uri, *param);
+           tnrParamM = param;
            }
-        pmtPidM = pid;
         }
      pidUpdateCacheM.Set(ePidUpdateIntervalMs);
      if (!rtspM.Play(*uri))
@@ -463,7 +536,7 @@ bool cSatipTuner::KeepAlive(bool forceP)
      forceP = true;
      }
   if (forceP && !isempty(*streamAddrM)) {
-     cString uri = cString::sprintf("rtsp://%s/", *streamAddrM);
+     cString uri = GetBaseUrl(*streamAddrM, streamPortM);
      if (!rtspM.Options(*uri))
         return false;
      }
@@ -480,7 +553,7 @@ bool cSatipTuner::ReadReceptionStatus(bool forceP)
      forceP = true;
      }
   if (forceP && !isempty(*streamAddrM) && (streamIdM > 0)) {
-     cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM);
+     cString uri = cString::sprintf("%sstream=%d", *GetBaseUrl(*streamAddrM, streamPortM), streamIdM);
      if (rtspM.Describe(*uri))
         return true;
      }
@@ -615,5 +688,5 @@ cString cSatipTuner::GetSignalStatus(void)
 cString cSatipTuner::GetInformation(void)
 {
   debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
-  return (currentStateM >= tsTuned) ? cString::sprintf("rtsp://%s/?%s [stream=%d]", *streamAddrM, *streamParamM, streamIdM) : "connection failed";
+  return (currentStateM >= tsTuned) ? cString::sprintf("%s?%s (%s) [stream=%d]", *GetBaseUrl(*streamAddrM, streamPortM), *streamParamM, *rtspM.GetActiveMode(), streamIdM) : "connection failed";
 }
diff --git a/tuner.h b/tuner.h
index 1c7f655..3d35cfd 100644
--- a/tuner.h
+++ b/tuner.h
@@ -69,6 +69,7 @@ public:
   void Set(cSatipServer *serverP, const int transponderP) { serverM = serverP; transponderM = transponderP; }
   void Reset(void) { serverM = NULL; transponderM = 0; }
   cString GetAddress(void) { return serverM ? cSatipDiscover::GetInstance()->GetServerAddress(serverM) : ""; }
+  int GetPort(void) { return serverM ? cSatipDiscover::GetInstance()->GetServerPort(serverM) : SATIP_DEFAULT_RTSP_PORT; }
   cString GetInfo(void) { return cString::sprintf("server=%s deviceid=%d transponder=%d", serverM ? "assigned" : "null", deviceIdM, transponderM); }
 };
 
@@ -98,6 +99,8 @@ private:
   cSatipRtcp rtcpM;
   cString streamAddrM;
   cString streamParamM;
+  cString tnrParamM;
+  int streamPortM;
   cSatipTunerServer currentServerM;
   cSatipTunerServer nextServerM;
   cMutex mutexM;
@@ -130,6 +133,7 @@ private:
   bool RequestState(eTunerState stateP, eStateMode modeP);
   const char *StateModeString(eStateMode modeP);
   const char *TunerStateString(eTunerState stateP);
+  cString GetBaseUrl(const char *addressP, const int portP);
 
 protected:
   virtual void Action(void);
@@ -153,8 +157,11 @@ public:
 public:
   virtual void ProcessVideoData(u_char *bufferP, int lengthP);
   virtual void ProcessApplicationData(u_char *bufferP, int lengthP);
+  virtual void ProcessRtpData(u_char *bufferP, int lengthP);
+  virtual void ProcessRtcpData(u_char *bufferP, int lengthP);
   virtual void SetStreamId(int streamIdP);
   virtual void SetSessionTimeout(const char *sessionP, int timeoutP);
+  virtual void SetupTransport(int rtpPortP, int rtcpPortP, const char *streamAddrP, const char *sourceAddrP);
   virtual int GetId(void);
 };
 
diff --git a/tunerif.h b/tunerif.h
index f5ea6a6..9eaac0c 100644
--- a/tunerif.h
+++ b/tunerif.h
@@ -14,8 +14,11 @@ public:
   virtual ~cSatipTunerIf() {}
   virtual void ProcessVideoData(u_char *bufferP, int lengthP) = 0;
   virtual void ProcessApplicationData(u_char *bufferP, int lengthP) = 0;
+  virtual void ProcessRtpData(u_char *bufferP, int lengthP) = 0;
+  virtual void ProcessRtcpData(u_char *bufferP, int lengthP) = 0;
   virtual void SetStreamId(int streamIdP) = 0;
   virtual void SetSessionTimeout(const char *sessionP, int timeoutP) = 0;
+  virtual void SetupTransport(int rtpPortP, int rtcpPortP, const char *streamAddrP, const char *sourceAddrP) = 0;
   virtual int GetId(void) = 0;
 
 private:

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-vdr-dvb/vdr-plugin-satip.git



More information about the pkg-vdr-dvb-changes mailing list