# Added support for DVB devices with more than one frontend that all use the same
# dvr and demux. Note that in order for this to work, you must not set symbolic
# links like "demux1 -> demux0" and "dvr1 -> dvr0", as is mentioned in some user
# manuals of multi frontend DVB cards.
#
--- dvbdevice.h	2017/05/09 11:24:47	5.0
+++ dvbdevice.h	2018/10/20 11:39:11
@@ -67,8 +67,6 @@
 
 // --- End of definitions for older DVB API versions -------------------------
 
-#define MAXDELIVERYSYSTEMS 8
-
 #define DEV_VIDEO         "/dev/video"
 #define DEV_DVB_BASE      "/dev/dvb"
 #define DEV_DVB_ADAPTER   "adapter"
@@ -162,12 +160,12 @@
 
 class cDvbTuner;
 
+cString DvbName(const char *Name, int Adapter, int Frontend);
+int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
+
 /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
 
 class cDvbDevice : public cDevice {
-protected:
-  static cString DvbName(const char *Name, int Adapter, int Frontend);
-  static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false);
 private:
   static bool Exists(int Adapter, int Frontend);
          ///< Checks whether the given adapter/frontend exists.
@@ -182,16 +180,11 @@
 protected:
   int adapter, frontend;
 private:
-  dvb_frontend_info frontendInfo;
-  int deliverySystems[MAXDELIVERYSYSTEMS];
-  int numDeliverySystems;
-  int numModulations;
   int fd_dvr, fd_ca;
   bool checkTsBuffer;
   static cMutex bondMutex;
   cDvbDevice *bondedDevice;
   mutable bool needsDetachBondedReceivers;
-  bool QueryDeliverySystems(int fd_frontend);
 public:
   cDvbDevice(int Adapter, int Frontend);
   virtual ~cDvbDevice();
--- dvbdevice.c	2018/02/15 15:37:01	5.0
+++ dvbdevice.c	2018/10/29 12:22:11
@@ -293,19 +293,248 @@
   return true;
 }
 
+// --- cDvbFrontend ----------------------------------------------------------
+
+const char *DeliverySystemNames[] = {
+  "???",
+  "DVB-C",
+  "DVB-C",
+  "DVB-T",
+  "DSS",
+  "DVB-S",
+  "DVB-S2",
+  "DVB-H",
+  "ISDBT",
+  "ISDBS",
+  "ISDBC",
+  "ATSC",
+  "ATSCMH",
+  "DTMB",
+  "CMMB",
+  "DAB",
+  "DVB-T2",
+  "TURBO",
+  "DVB-C",
+  "DVB-C2",
+  NULL
+  };
+
+static const int DeliverySystemNamesMax = sizeof(DeliverySystemNames) / sizeof(DeliverySystemNames[0]) - 2; // -1 to get the maximum allowed index & -1 for the NULL => -2
+
+static const char *GetDeliverySystemName(int Index)
+{
+  if (Index > DeliverySystemNamesMax)
+     Index = 0;
+  return DeliverySystemNames[Index];
+};
+
+#define MAXFRONTENDCMDS 16
+#define SETCMD(c, d) { Props[CmdSeq.num].cmd = (c);\
+                       Props[CmdSeq.num].u.data = (d);\
+                       if (CmdSeq.num++ > MAXFRONTENDCMDS) {\
+                          esyslog("ERROR: too many tuning commands on frontend %d/%d", adapter, frontend);\
+                          return false;\
+                          }\
+                     }
+
+class cDvbFrontend {
+private:
+  int adapter, frontend;
+  int fd_frontend;
+  uint32_t subsystemId;
+  dvb_frontend_info frontendInfo;
+  cVector<int> deliverySystems;
+  int numModulations;
+  bool QueryDeliverySystems(void);
+public:
+  cDvbFrontend(int Adapter, int Frontend);
+  ~cDvbFrontend();
+  int Open(void);
+  void Close(void);
+  const char *FrontendName(void) { return frontendInfo.name; }
+  bool ProvidesDeliverySystem(int DeliverySystem) const;
+  bool ProvidesModulation(int System, int StreamId, int Modulation) const;
+  int NumDeliverySystems(void) const { return deliverySystems.Size(); }
+  int NumModulations(void) const { return numModulations; }
+  uint32_t SubsystemId(void) const { return subsystemId; }
+  };
+
+cDvbFrontend::cDvbFrontend(int Adapter, int Frontend)
+{
+  adapter = Adapter;
+  frontend = Frontend;
+  fd_frontend = -1;
+  subsystemId = cDvbDeviceProbe::GetSubsystemId(adapter, frontend);
+  numModulations = 0;
+  Open();
+  QueryDeliverySystems();
+  Close();
+}
+
+cDvbFrontend::~cDvbFrontend()
+{
+  Close();
+}
+
+int cDvbFrontend::Open(void)
+{
+  Close();
+  fd_frontend = DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK, true);
+  return fd_frontend;
+}
+
+void cDvbFrontend::Close(void)
+{
+  if (fd_frontend >= 0) {
+     if (close(fd_frontend) != 0)
+        esyslog("ERROR: frontend %d/%d", adapter, frontend);
+     fd_frontend = -1;
+     }
+}
+
+bool cDvbFrontend::ProvidesDeliverySystem(int DeliverySystem) const
+{
+  for (int i = 0; i < deliverySystems.Size(); i++) {
+      if (deliverySystems[i] == DeliverySystem)
+         return true;
+      }
+  return false;
+}
+
+bool cDvbFrontend::ProvidesModulation(int System, int StreamId, int Modulation) const
+{
+  if (StreamId != 0 && !(frontendInfo.caps & FE_CAN_MULTISTREAM))
+     return false;
+  if (Modulation == QPSK     && !(frontendInfo.caps & FE_CAN_QPSK) ||
+      Modulation == QAM_16   && !(frontendInfo.caps & FE_CAN_QAM_16) ||
+      Modulation == QAM_32   && !(frontendInfo.caps & FE_CAN_QAM_32) ||
+      Modulation == QAM_64   && !(frontendInfo.caps & FE_CAN_QAM_64) ||
+      Modulation == QAM_128  && !(frontendInfo.caps & FE_CAN_QAM_128) ||
+      Modulation == QAM_256  && !(frontendInfo.caps & FE_CAN_QAM_256) ||
+      Modulation == QAM_AUTO && !(frontendInfo.caps & FE_CAN_QAM_AUTO) ||
+      Modulation == VSB_8    && !(frontendInfo.caps & FE_CAN_8VSB) ||
+      Modulation == VSB_16   && !(frontendInfo.caps & FE_CAN_16VSB) ||
+      Modulation == PSK_8    && !(frontendInfo.caps & FE_CAN_TURBO_FEC) && System == SYS_DVBS) // "turbo fec" is a non standard FEC used by North American broadcasters - this is a best guess to de
+     return false;
+  return true;
+}
+
+bool cDvbFrontend::QueryDeliverySystems(void)
+{
+  deliverySystems.Clear();
+  numModulations = 0;
+  if (ioctl(fd_frontend, FE_GET_INFO, &frontendInfo) < 0) {
+     LOG_ERROR;
+     return false;
+     }
+  dtv_property Props[1];
+  dtv_properties CmdSeq;
+  // Determine the version of the running DVB API:
+  if (!DvbApiVersion) {
+     memset(&Props, 0, sizeof(Props));
+     memset(&CmdSeq, 0, sizeof(CmdSeq));
+     CmdSeq.props = Props;
+     SETCMD(DTV_API_VERSION, 0);
+     if (ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq) != 0) {
+        LOG_ERROR;
+        return false;
+        }
+     DvbApiVersion = Props[0].u.data;
+     isyslog("DVB API version is 0x%04X (VDR was built with 0x%04X)", DvbApiVersion, DVBAPIVERSION);
+     }
+  // Determine the types of delivery systems this device provides:
+  bool LegacyMode = true;
+  if (DvbApiVersion >= 0x0505) {
+     memset(&Props, 0, sizeof(Props));
+     memset(&CmdSeq, 0, sizeof(CmdSeq));
+     CmdSeq.props = Props;
+     SETCMD(DTV_ENUM_DELSYS, 0);
+     int Result = ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq);
+     if (Result == 0) {
+        for (uint i = 0; i < Props[0].u.buffer.len; i++) {
+            // activate this line to simulate a multi-frontend device if you only have a single-frontend device with DVB-S and DVB-S2:
+            //if (frontend == 0 && Props[0].u.buffer.data[i] != SYS_DVBS || frontend == 1 && Props[0].u.buffer.data[i] != SYS_DVBS2)
+            deliverySystems.Append(Props[0].u.buffer.data[i]);
+            }
+        LegacyMode = false;
+        }
+     else {
+        esyslog("ERROR: can't query delivery systems on frontend %d/%d - falling back to legacy mode", adapter, frontend);
+        }
+     }
+  if (LegacyMode) {
+     // Legacy mode (DVB-API < 5.5):
+     switch (frontendInfo.type) {
+       case FE_QPSK: deliverySystems.Append(SYS_DVBS);
+                     if (frontendInfo.caps & FE_CAN_2G_MODULATION)
+                        deliverySystems.Append(SYS_DVBS2);
+                     break;
+       case FE_OFDM: deliverySystems.Append(SYS_DVBT);
+                     if (frontendInfo.caps & FE_CAN_2G_MODULATION)
+                        deliverySystems.Append(SYS_DVBT2);
+                     break;
+       case FE_QAM:  deliverySystems.Append(SYS_DVBC_ANNEX_AC); break;
+       case FE_ATSC: deliverySystems.Append(SYS_ATSC); break;
+       default: esyslog("ERROR: unknown frontend type %d on frontend %d/%d", frontendInfo.type, adapter, frontend);
+       }
+     }
+  if (deliverySystems.Size() > 0) {
+     cString ds("");
+     for (int i = 0; i < deliverySystems.Size(); i++)
+         ds = cString::sprintf("%s%s%s", *ds, i ? "," : "", GetDeliverySystemName(deliverySystems[i]));
+     cString ms("");
+     if (frontendInfo.caps & FE_CAN_QPSK)      { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QPSK, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_QAM_16)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_16, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_QAM_32)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_32, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_QAM_64)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_64, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_QAM_128)   { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_128, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_QAM_256)   { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_256, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_8VSB)      { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(VSB_8, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_16VSB)     { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(VSB_16, ModulationValues)); }
+     if (frontendInfo.caps & FE_CAN_TURBO_FEC) { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", "TURBO_FEC"); }
+     if (!**ms)
+        ms = "unknown modulations";
+     isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, *ds, *ms, frontendInfo.name);
+     return true;
+     }
+  else
+     esyslog("ERROR: frontend %d/%d doesn't provide any delivery systems", adapter, frontend);
+  return false;
+}
+
 // --- cDvbTuner -------------------------------------------------------------
 
 #define TUNER_POLL_TIMEOUT  10 // ms
 
+static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTransponderParameters *Dtp)
+{
+  int ds = SYS_UNDEFINED;
+  if (Channel->IsAtsc())
+     ds = SYS_ATSC;
+  else if (Channel->IsCable())
+     ds = SYS_DVBC_ANNEX_AC;
+  else if (Channel->IsSat())
+     ds = Dtp->System() == DVB_SYSTEM_1 ? SYS_DVBS : SYS_DVBS2;
+  else if (Channel->IsTerr())
+     ds = Dtp->System() == DVB_SYSTEM_1 ? SYS_DVBT : SYS_DVBT2;
+  else
+     esyslog("ERROR: can't determine frontend type for channel %d (%s)", Channel->Number(), Channel->Name());
+  return ds;
+}
+
 class cDvbTuner : public cThread {
 private:
   static cMutex bondMutex;
   enum eTunerStatus { tsIdle, tsSet, tsPositioning, tsTuned, tsLocked };
   int frontendType;
   const cDvbDevice *device;
-  int fd_frontend;
-  int adapter, frontend;
-  uint32_t subsystemId;
+  mutable int fd_frontend;
+  int adapter;
+  mutable int frontend;
+  cVector<cDvbFrontend *> dvbFrontends;
+  mutable cDvbFrontend *dvbFrontend;
+  int numDeliverySystems;
+  int numModulations;
   int tuneTimeout;
   int lockTimeout;
   time_t lastTimeoutReport;
@@ -320,7 +549,7 @@
   const cScr *scr;
   bool lnbPowerTurnedOn;
   eTunerStatus tunerStatus;
-  cMutex mutex;
+  mutable cMutex mutex;
   cCondVar locked;
   cCondVar newSet;
   cDvbTuner *bondedTuner;
@@ -337,14 +566,19 @@
   bool SetFrontend(void);
   virtual void Action(void);
 public:
-  cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend);
+  cDvbTuner(const cDvbDevice *Device, int Adapter, int Frontend);
   virtual ~cDvbTuner();
+  bool ProvidesDeliverySystem(int DeliverySystem) const;
+  bool ProvidesModulation(int System, int StreamId, int Modulation) const;
+  bool ProvidesFrontend(const cChannel *Channel, bool Activate = false) const;
   int FrontendType(void) const { return frontendType; }
+  const char *FrontendName(void) { return dvbFrontend->FrontendName(); }
+  int NumProvidedSystems(void) const { return numDeliverySystems + numModulations; }
   bool Bond(cDvbTuner *Tuner);
   void UnBond(void);
   bool BondingOk(const cChannel *Channel, bool ConsiderOccupied = false) const;
   const cChannel *GetTransponder(void) const { return &channel; }
-  uint32_t SubsystemId(void) const { return subsystemId; }
+  uint32_t SubsystemId(void) const { return dvbFrontend->SubsystemId(); }
   bool IsTunedTo(const cChannel *Channel) const;
   void SetChannel(const cChannel *Channel);
   bool Locked(int TimeoutMs = 0);
@@ -356,14 +590,14 @@
 
 cMutex cDvbTuner::bondMutex;
 
-cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Fd_Frontend, int Adapter, int Frontend)
+cDvbTuner::cDvbTuner(const cDvbDevice *Device, int Adapter, int Frontend)
 {
   frontendType = SYS_UNDEFINED;
   device = Device;
-  fd_frontend = Fd_Frontend;
+  fd_frontend = -1;
   adapter = Adapter;
   frontend = Frontend;
-  subsystemId = cDvbDeviceProbe::GetSubsystemId(adapter, frontend);
+  dvbFrontend = NULL;
   tuneTimeout = 0;
   lockTimeout = 0;
   lastTimeoutReport = 0;
@@ -379,7 +613,31 @@
   tunerStatus = tsIdle;
   bondedTuner = NULL;
   bondedMaster = false;
-  SetDescription("frontend %d/%d tuner", adapter, frontend);
+  cDvbFrontend *fe = new cDvbFrontend(adapter, frontend);
+  dvbFrontends.Append(fe);
+  numDeliverySystems = fe->NumDeliverySystems();
+  numModulations = fe->NumModulations();
+  cString FrontendNumbers = cString::sprintf("%d", frontend);
+  // Check for multiple frontends:
+  if (frontend == 0) {
+     for (int i = 1; ; i++) {
+         if (access(DvbName(DEV_DVB_FRONTEND, adapter, i), F_OK) == 0) {
+            if (access(DvbName(DEV_DVB_DEMUX, adapter, i), F_OK) != 0) {
+               fe = new cDvbFrontend(adapter, i);
+               dvbFrontends.Append(fe);
+               numDeliverySystems += fe->NumDeliverySystems();
+               //numModulations += fe->NumModulations(); // looks like in multi frontend devices all frontends report the same modulations
+               FrontendNumbers = cString::sprintf("%s+%d", *FrontendNumbers, i);
+               }
+            }
+         else
+            break;
+         }
+     }
+  // Open default frontend:
+  dvbFrontend = dvbFrontends[0];
+  fd_frontend = dvbFrontend->Open();
+  SetDescription("frontend %d/%s tuner", adapter, *FrontendNumbers);
   Start();
 }
 
@@ -396,6 +654,49 @@
      ExecuteDiseqc(lastDiseqc, &Frequency);
      }
   */
+  for (int i = 0; i < dvbFrontends.Size(); i++)
+      delete dvbFrontends[i];
+}
+
+bool cDvbTuner::ProvidesDeliverySystem(int DeliverySystem) const
+{
+  for (int i = 0; i < dvbFrontends.Size(); i++) {
+      if (dvbFrontends[i]->ProvidesDeliverySystem(DeliverySystem))
+         return true;
+      }
+  return false;
+}
+
+bool cDvbTuner::ProvidesModulation(int System, int StreamId, int Modulation) const
+{
+  for (int i = 0; i < dvbFrontends.Size(); i++) {
+      if (dvbFrontends[i]->ProvidesModulation(System, StreamId, Modulation))
+         return true;
+      }
+  return false;
+}
+
+bool cDvbTuner::ProvidesFrontend(const cChannel *Channel, bool Activate) const
+{
+  cDvbTransponderParameters dtp(Channel->Parameters());
+  int DeliverySystem = GetRequiredDeliverySystem(Channel, &dtp);
+  for (int i = 0; i < dvbFrontends.Size(); i++) {
+      if (dvbFrontends[i]->ProvidesDeliverySystem(DeliverySystem) && dvbFrontends[i]->ProvidesModulation(dtp.System(), dtp.StreamId(), dtp.Modulation())) {
+         if (Activate && dvbFrontend != dvbFrontends[i]) {
+            cMutexLock MutexLock(&mutex);
+            dvbFrontend->Close();
+            dvbFrontend = dvbFrontends[i];
+            fd_frontend = dvbFrontend->Open();
+            frontend = i;
+            dsyslog("using frontend %d/%d", adapter, frontend);
+            lastUncValue = 0;
+            lastUncDelta = 0;
+            lastUncChange = 0;
+            }
+         return true;
+         }
+      }
+  return false;
 }
 
 bool cDvbTuner::Bond(cDvbTuner *Tuner)
@@ -565,15 +866,6 @@
 //#define DEBUG_SIGNALSTRENGTH
 //#define DEBUG_SIGNALQUALITY
 
-#define MAXFRONTENDCMDS 16
-#define SETCMD(c, d) { Props[CmdSeq.num].cmd = (c);\
-                       Props[CmdSeq.num].u.data = (d);\
-                       if (CmdSeq.num++ > MAXFRONTENDCMDS) {\
-                          esyslog("ERROR: too many tuning commands on frontend %d/%d", adapter, frontend);\
-                          return false;\
-                          }\
-                     }
-
 bool cDvbTuner::GetSignalStats(int &Valid, double *Strength, double *Cnr, double *BerPre, double *BerPost, double *Per, int *Status) const
 {
   ClearEventQueue();
@@ -585,7 +877,7 @@
   CmdSeq.props = Props;
   Valid = DTV_STAT_VALID_NONE;
   if (ioctl(fd_frontend, FE_READ_STATUS, &FeStatus) != 0) {
-     esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
+     esyslog("ERROR: frontend %d/%d: %m (%s:%d)", adapter, frontend, __FILE__, __LINE__);
      return false;
      }
   if (Status) {
@@ -606,7 +898,7 @@
   if (Per)      { SETCMD(DTV_STAT_ERROR_BLOCK_COUNT, 0);
                   SETCMD(DTV_STAT_TOTAL_BLOCK_COUNT, 0); }
   if (ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq) != 0) {
-     esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
+     esyslog("ERROR: frontend %d/%d: %m (%s:%d)", adapter, frontend, __FILE__, __LINE__);
      return false;
      }
   int i = 0;
@@ -939,7 +1231,7 @@
       SETCMD(DTV_CODE_RATE_HP, 0); // DVB-T only
       SETCMD(DTV_INNER_FEC, 0);
       if (ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq) != 0) {
-         esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
+         esyslog("ERROR: frontend %d/%d: %m (%s:%d)", adapter, frontend, __FILE__, __LINE__);
          return -1;
          }
       int FeMod = (Props[1].u.st.len > 0) ? (int)Props[1].u.data : -1;
@@ -973,7 +1265,7 @@
   uint16_t MaxSignal = 0xFFFF; // Let's assume the default is using the entire range.
   // Use the subsystemId to identify individual devices in case they need
   // special treatment to map their Signal value into the range 0...0xFFFF.
-  switch (subsystemId) {
+  switch (dvbFrontend->SubsystemId()) {
     case 0x13C21019: // TT-budget S2-3200 (DVB-S/DVB-S2)
     case 0x1AE40001: // TechniSat SkyStar HD2 (DVB-S/DVB-S2)
                      MaxSignal = 670; break;
@@ -982,7 +1274,7 @@
   if (s > 100)
      s = 100;
 #ifdef DEBUG_SIGNALSTRENGTH
-  fprintf(stderr, "FE %d/%d: API3 %08X S = %04X %04X %3d%%\n", adapter, frontend, subsystemId, MaxSignal, Signal, s);
+  fprintf(stderr, "FE %d/%d: API3 %08X S = %04X %04X %3d%%\n", adapter, frontend, dvbFrontend->SubsystemId(), MaxSignal, Signal, s);
 #endif
   return s;
 }
@@ -1005,7 +1297,7 @@
       SETCMD(DTV_STAT_POST_ERROR_BIT_COUNT, 0);
       SETCMD(DTV_STAT_POST_TOTAL_BIT_COUNT, 0);
       if (ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq) != 0) {
-         esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
+         esyslog("ERROR: frontend %d/%d: %m (%s:%d)", adapter, frontend, __FILE__, __LINE__);
          return -1;
          }
       int FeMod = (Props[1].u.st.len > 0) ? (int)Props[1].u.data : -1;
@@ -1132,7 +1424,7 @@
      uint16_t MaxSnr = 0xFFFF; // Let's assume the default is using the entire range.
      // Use the subsystemId to identify individual devices in case they need
      // special treatment to map their Snr value into the range 0...0xFFFF.
-     switch (subsystemId) {
+     switch (dvbFrontend->SubsystemId()) {
        case 0x13C21019: // TT-budget S2-3200 (DVB-S/DVB-S2)
        case 0x1AE40001: // TechniSat SkyStar HD2 (DVB-S/DVB-S2)
                         if (frontendType == SYS_DVBS2) {
@@ -1154,7 +1446,7 @@
      if (q > 100)
         q = 100;
 #ifdef DEBUG_SIGNALQUALITY
-     fprintf(stderr, "FE %d/%d: API3 %08X Q = %04X %04X %d %5d %5d %3d%%\n", adapter, frontend, subsystemId, MaxSnr, Snr, HasSnr, HasBer ? int(Ber) : -1, HasUnc ? int(Unc) : -1, q);
+     fprintf(stderr, "FE %d/%d: API3 %08X Q = %04X %04X %d %5d %5d %3d%%\n", adapter, frontend, dvbFrontend->SubsystemId(), MaxSnr, Snr, HasSnr, HasBer ? int(Ber) : -1, HasUnc ? int(Unc) : -1, q);
 #endif
      return q;
      }
@@ -1240,22 +1532,6 @@
   CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF));
 }
 
-static int GetRequiredDeliverySystem(const cChannel *Channel, const cDvbTransponderParameters *Dtp)
-{
-  int ds = SYS_UNDEFINED;
-  if (Channel->IsAtsc())
-     ds = SYS_ATSC;
-  else if (Channel->IsCable())
-     ds = SYS_DVBC_ANNEX_AC;
-  else if (Channel->IsSat())
-     ds = Dtp->System() == DVB_SYSTEM_1 ? SYS_DVBS : SYS_DVBS2;
-  else if (Channel->IsTerr())
-     ds = Dtp->System() == DVB_SYSTEM_1 ? SYS_DVBT : SYS_DVBT2;
-  else
-     esyslog("ERROR: can't determine frontend type for channel %d (%s)", Channel->Number(), Channel->Name());
-  return ds;
-}
-
 bool cDvbTuner::SetFrontend(void)
 {
   dtv_property Props[MAXFRONTENDCMDS];
@@ -1265,7 +1541,7 @@
   CmdSeq.props = Props;
   SETCMD(DTV_CLEAR, 0);
   if (ioctl(fd_frontend, FE_SET_PROPERTY, &CmdSeq) < 0) {
-     esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
+     esyslog("ERROR: frontend %d/%d: %m (%s:%d)", adapter, frontend, __FILE__, __LINE__);
      return false;
      }
   CmdSeq.num = 0;
@@ -1390,7 +1666,7 @@
      }
   SETCMD(DTV_TUNE, 0);
   if (ioctl(fd_frontend, FE_SET_PROPERTY, &CmdSeq) < 0) {
-     esyslog("ERROR: frontend %d/%d: %m", adapter, frontend);
+     esyslog("ERROR: frontend %d/%d: %m (%s:%d)", adapter, frontend, __FILE__, __LINE__);
      return false;
      }
   return true;
@@ -1402,11 +1678,11 @@
   bool LostLock = false;
   fe_status_t Status = (fe_status_t)0;
   while (Running()) {
+        int WaitTime = 1000;
         fe_status_t NewStatus;
         if (GetFrontendStatus(NewStatus))
            Status = NewStatus;
         cMutexLock MutexLock(&mutex);
-        int WaitTime = 1000;
         switch (tunerStatus) {
           case tsIdle:
                break; // we want the TimedWait() below!
@@ -1541,55 +1817,16 @@
 int cDvbDevice::setTransferModeForDolbyDigital = 1;
 cMutex cDvbDevice::bondMutex;
 
-const char *DeliverySystemNames[] = {
-  "",
-  "DVB-C",
-  "DVB-C",
-  "DVB-T",
-  "DSS",
-  "DVB-S",
-  "DVB-S2",
-  "DVB-H",
-  "ISDBT",
-  "ISDBS",
-  "ISDBC",
-  "ATSC",
-  "ATSCMH",
-  "DTMB",
-  "CMMB",
-  "DAB",
-  "DVB-T2",
-  "TURBO",
-  "DVB-C",
-  "DVB-C2",
-  NULL
-  };
-
-static const int DeliverySystemNamesMax = sizeof(DeliverySystemNames) / sizeof(DeliverySystemNames[0]) - 2; // -1 to get the maximum allowed index & -1 for the NULL => -2
-
-static const char *GetDeliverySystemName(int Index)
-{
-  if (Index > DeliverySystemNamesMax)
-     Index = 0;
-  return DeliverySystemNames[Index];
-};
-
 cDvbDevice::cDvbDevice(int Adapter, int Frontend)
 {
   adapter = Adapter;
   frontend = Frontend;
   ciAdapter = NULL;
   dvbTuner = NULL;
-  numDeliverySystems = 0;
-  numModulations = 0;
   bondedDevice = NULL;
   needsDetachBondedReceivers = false;
   tsBuffer = NULL;
 
-  // Devices that are present on all card types:
-
-  int fd_frontend = DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
-
   // Common Interface:
 
   fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR);
@@ -1603,12 +1840,7 @@
 
   // We only check the devices that must be present - the others will be checked before accessing them://XXX
 
-  if (fd_frontend >= 0) {
-     if (QueryDeliverySystems(fd_frontend))
-        dvbTuner = new cDvbTuner(this, fd_frontend, adapter, frontend);
-     }
-  else
-     esyslog("ERROR: can't open DVB device %d/%d", adapter, frontend);
+  dvbTuner = new cDvbTuner(this, adapter, frontend);
 
   StartSectionHandler();
 }
@@ -1623,12 +1855,12 @@
   // caused segfaults. Besides, the program is about to terminate anyway...
 }
 
-cString cDvbDevice::DvbName(const char *Name, int Adapter, int Frontend)
+cString DvbName(const char *Name, int Adapter, int Frontend)
 {
   return cString::sprintf("%s/%s%d/%s%d", DEV_DVB_BASE, DEV_DVB_ADAPTER, Adapter, Name, Frontend);
 }
 
-int cDvbDevice::DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError)
+int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError)
 {
   cString FileName = DvbName(Name, Adapter, Frontend);
   int fd = open(FileName, Mode);
@@ -1669,18 +1901,16 @@
 
 cString cDvbDevice::DeviceType(void) const
 {
-  if (dvbTuner) {
-     if (dvbTuner->FrontendType() != SYS_UNDEFINED)
-        return GetDeliverySystemName(dvbTuner->FrontendType());
-     if (numDeliverySystems)
-        return GetDeliverySystemName(deliverySystems[0]); // to have some reasonable default
-     }
+  if (dvbTuner)
+     return GetDeliverySystemName(dvbTuner->FrontendType());
   return "";
 }
 
 cString cDvbDevice::DeviceName(void) const
 {
-  return frontendInfo.name;
+  if (dvbTuner)
+     return dvbTuner->FrontendName();
+  return "";
 }
 
 bool cDvbDevice::Initialize(void)
@@ -1702,7 +1932,8 @@
                  while ((f = AdapterDir.Next()) != NULL) {
                        if (strstr(f->d_name, DEV_DVB_FRONTEND) == f->d_name) {
                           int Frontend = strtol(f->d_name + strlen(DEV_DVB_FRONTEND), NULL, 10);
-                          Nodes.Append(strdup(cString::sprintf("%2d %2d", Adapter, Frontend)));
+                          if (access(DvbName(DEV_DVB_DEMUX, Adapter, Frontend), F_OK) == 0) // we only create devices for actual demuxes
+                             Nodes.Append(strdup(cString::sprintf("%2d %2d", Adapter, Frontend)));
                           }
                        }
                  }
@@ -1741,90 +1972,6 @@
   return Found > 0;
 }
 
-bool cDvbDevice::QueryDeliverySystems(int fd_frontend)
-{
-  numDeliverySystems = 0;
-  if (ioctl(fd_frontend, FE_GET_INFO, &frontendInfo) < 0) {
-     LOG_ERROR;
-     return false;
-     }
-  dtv_property Props[1];
-  dtv_properties CmdSeq;
-  // Determine the version of the running DVB API:
-  if (!DvbApiVersion) {
-     memset(&Props, 0, sizeof(Props));
-     memset(&CmdSeq, 0, sizeof(CmdSeq));
-     CmdSeq.props = Props;
-     SETCMD(DTV_API_VERSION, 0);
-     if (ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq) != 0) {
-        LOG_ERROR;
-        return false;
-        }
-     DvbApiVersion = Props[0].u.data;
-     isyslog("DVB API version is 0x%04X (VDR was built with 0x%04X)", DvbApiVersion, DVBAPIVERSION);
-     }
-  // Determine the types of delivery systems this device provides:
-  bool LegacyMode = true;
-  if (DvbApiVersion >= 0x0505) {
-     memset(&Props, 0, sizeof(Props));
-     memset(&CmdSeq, 0, sizeof(CmdSeq));
-     CmdSeq.props = Props;
-     SETCMD(DTV_ENUM_DELSYS, 0);
-     int Result = ioctl(fd_frontend, FE_GET_PROPERTY, &CmdSeq);
-     if (Result == 0) {
-        for (uint i = 0; i < Props[0].u.buffer.len; i++) {
-            if (numDeliverySystems >= MAXDELIVERYSYSTEMS) {
-               esyslog("ERROR: too many delivery systems on frontend %d/%d", adapter, frontend);
-               break;
-               }
-            deliverySystems[numDeliverySystems++] = Props[0].u.buffer.data[i];
-            }
-        LegacyMode = false;
-        }
-     else {
-        esyslog("ERROR: can't query delivery systems on frontend %d/%d - falling back to legacy mode", adapter, frontend);
-        }
-     }
-  if (LegacyMode) {
-     // Legacy mode (DVB-API < 5.5):
-     switch (frontendInfo.type) {
-       case FE_QPSK: deliverySystems[numDeliverySystems++] = SYS_DVBS;
-                     if (frontendInfo.caps & FE_CAN_2G_MODULATION)
-                        deliverySystems[numDeliverySystems++] = SYS_DVBS2;
-                     break;
-       case FE_OFDM: deliverySystems[numDeliverySystems++] = SYS_DVBT;
-                     if (frontendInfo.caps & FE_CAN_2G_MODULATION)
-                        deliverySystems[numDeliverySystems++] = SYS_DVBT2;
-                     break;
-       case FE_QAM:  deliverySystems[numDeliverySystems++] = SYS_DVBC_ANNEX_AC; break;
-       case FE_ATSC: deliverySystems[numDeliverySystems++] = SYS_ATSC; break;
-       default: esyslog("ERROR: unknown frontend type %d on frontend %d/%d", frontendInfo.type, adapter, frontend);
-       }
-     }
-  if (numDeliverySystems > 0) {
-     cString ds("");
-     for (int i = 0; i < numDeliverySystems; i++)
-         ds = cString::sprintf("%s%s%s", *ds, i ? "," : "", GetDeliverySystemName(deliverySystems[i]));
-     cString ms("");
-     if (frontendInfo.caps & FE_CAN_QPSK)      { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QPSK, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_QAM_16)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_16, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_QAM_32)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_32, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_QAM_64)    { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_64, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_QAM_128)   { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_128, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_QAM_256)   { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(QAM_256, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_8VSB)      { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(VSB_8, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_16VSB)     { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", MapToUserString(VSB_16, ModulationValues)); }
-     if (frontendInfo.caps & FE_CAN_TURBO_FEC) { numModulations++; ms = cString::sprintf("%s%s%s", *ms, **ms ? "," : "", "TURBO_FEC"); }
-     if (!**ms)
-        ms = "unknown modulations";
-     isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, *ds, *ms, frontendInfo.name);
-     return true;
-     }
-  else
-     esyslog("ERROR: frontend %d/%d doesn't provide any delivery systems", adapter, frontend);
-  return false;
-}
-
 bool cDvbDevice::BondDevices(const char *Bondings)
 {
   UnBondDevices();
@@ -1999,11 +2146,7 @@
 
 bool cDvbDevice::ProvidesDeliverySystem(int DeliverySystem) const
 {
-  for (int i = 0; i < numDeliverySystems; i++) {
-      if (deliverySystems[i] == DeliverySystem)
-         return true;
-      }
-  return false;
+  return dvbTuner->ProvidesDeliverySystem(DeliverySystem);
 }
 
 bool cDvbDevice::ProvidesSource(int Source) const
@@ -2020,20 +2163,9 @@
 {
   if (!ProvidesSource(Channel->Source()))
      return false; // doesn't provide source
-  cDvbTransponderParameters dtp(Channel->Parameters());
-  if (!ProvidesDeliverySystem(GetRequiredDeliverySystem(Channel, &dtp)) ||
-     dtp.StreamId()   != 0        && !(frontendInfo.caps & FE_CAN_MULTISTREAM) ||
-     dtp.Modulation() == QPSK     && !(frontendInfo.caps & FE_CAN_QPSK) ||
-     dtp.Modulation() == QAM_16   && !(frontendInfo.caps & FE_CAN_QAM_16) ||
-     dtp.Modulation() == QAM_32   && !(frontendInfo.caps & FE_CAN_QAM_32) ||
-     dtp.Modulation() == QAM_64   && !(frontendInfo.caps & FE_CAN_QAM_64) ||
-     dtp.Modulation() == QAM_128  && !(frontendInfo.caps & FE_CAN_QAM_128) ||
-     dtp.Modulation() == QAM_256  && !(frontendInfo.caps & FE_CAN_QAM_256) ||
-     dtp.Modulation() == QAM_AUTO && !(frontendInfo.caps & FE_CAN_QAM_AUTO) ||
-     dtp.Modulation() == VSB_8    && !(frontendInfo.caps & FE_CAN_8VSB) ||
-     dtp.Modulation() == VSB_16   && !(frontendInfo.caps & FE_CAN_16VSB) ||
-     dtp.Modulation() == PSK_8    && !(frontendInfo.caps & FE_CAN_TURBO_FEC) && dtp.System() == SYS_DVBS) // "turbo fec" is a non standard FEC used by North American broadcasters - this is a best guess to determine this condition
+  if (!dvbTuner->ProvidesFrontend(Channel))
      return false; // requires modulation system which frontend doesn't provide
+  cDvbTransponderParameters dtp(Channel->Parameters());
   if (!cSource::IsSat(Channel->Source()) ||
      (!Setup.DiSEqC || Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization(), NULL)))
      return DeviceHooksProvidesTransponder(Channel);
@@ -2047,7 +2179,7 @@
   bool needsDetachReceivers = false;
   needsDetachBondedReceivers = false;
 
-  if (dvbTuner && ProvidesTransponder(Channel)) {
+  if (ProvidesTransponder(Channel)) {
      result = hasPriority;
      if (Priority > IDLEPRIORITY) {
         if (Receiving()) {
@@ -2097,7 +2229,7 @@
 
 int cDvbDevice::NumProvidedSystems(void) const
 {
-  return numDeliverySystems + numModulations;
+  return dvbTuner ? dvbTuner->NumProvidedSystems() : 0;
 }
 
 const cPositioner *cDvbDevice::Positioner(void) const
@@ -2137,9 +2269,11 @@
 
 bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
 {
-  if (dvbTuner)
+  if (dvbTuner->ProvidesFrontend(Channel, true)) {
      dvbTuner->SetChannel(Channel);
-  return true;
+     return true;
+     }
+  return false;
 }
 
 bool cDvbDevice::HasLock(int TimeoutMs) const
