[v2] multi-frontend-support for vdr 1.7.21

Message ID 4ED6C131.1000704@flensrocker.de
State New
Headers

Commit Message

L. Hanisch Nov. 30, 2011, 11:50 p.m. UTC
  Hi,

  Here's version 2 of my multi-frontend-patch. It's still "dirty", since it changes the constructor of cDvbDevice which 
will break compilation of some plugins. But I think it might be necessary to look at the relevant plugins since they 
might need to react on frontend changes. I haven't tested any of those plugins but will have a look at some that I'm 
using. Maybe there have to be some virtual functions like "BeforeFrontendSwitch" and "AfterFrontendSwitch" so the 
plugins are even able to know about it.

  Assumption for this patch:
  All frontends within one adapter have to be used mutually exclusive. All cards I know behave in this way. If there are 
cards with multiple frontends which can be used simultaneously I'd like to hear about it.

  Whenever the dvb-api-changes are upstream (the ENUM_DELSYS thingy) I think my patch can easily be converted to use that.

  I'm still working on this patch, it's not finished yet... :-)

  Have fun,

Lars.
  

Comments

Hawes, Mark Dec. 1, 2011, 11:08 a.m. UTC | #1
Hi Lars,
First reports on v2 of your multi-frontend patch with HVR 4000 card:
  - can switch between both frontends successfully and very stable with
repetitive tests 
  - timer behaviour as expected
  - switching response seems quicker than before
  - Streamdev and xineliboutput plugins compile OK. Xlo tested OK, will
look at Sd later
  - Have modified Rotor plugin to fit (maintaining personal version) and
all seems OK
  - Working through sc plugin changes to fit.
If I can get the sc plugin working I'll move across a sd premium card
into the mix and see how it behaves, watch this space ...       
While this is all good obviously things will no doubt change when Klaus
releases 2.x with the new multi-frontend adapter handling. However,
reading between the lines this may not be in the immediate future so an
interim workaround  for these cards is appreciated by me and I expect
others ...
Thanks and keep up the good work.
Mark.


-----Original Message-----
From: vdr-bounces@linuxtv.org [mailto:vdr-bounces@linuxtv.org] On Behalf
Of L. Hanisch
Sent: Thursday, 1 December 2011 10:50 AM
To: VDR Mailing List
Subject: [vdr] [PATCH v2] multi-frontend-support for vdr 1.7.21

Hi,

  Here's version 2 of my multi-frontend-patch. It's still "dirty", since
it changes the constructor of cDvbDevice which will break compilation of
some plugins. But I think it might be necessary to look at the relevant
plugins since they might need to react on frontend changes. I haven't
tested any of those plugins but will have a look at some that I'm using.
Maybe there have to be some virtual functions like
"BeforeFrontendSwitch" and "AfterFrontendSwitch" so the plugins are even
able to know about it.

  Assumption for this patch:
  All frontends within one adapter have to be used mutually exclusive.
All cards I know behave in this way. If there are cards with multiple
frontends which can be used simultaneously I'd like to hear about it.

  Whenever the dvb-api-changes are upstream (the ENUM_DELSYS thingy) I
think my patch can easily be converted to use that.

  I'm still working on this patch, it's not finished yet... :-)

  Have fun,

Lars.
  
Richard Scobie Dec. 1, 2011, 6:48 p.m. UTC | #2
L. Hanisch wrote:

>  Assumption for this patch:
>  All frontends within one adapter have to be used mutually exclusive. 
> All cards I know behave in this way. If there are cards with multiple 
> frontends which can be used simultaneously I'd like to hear about it.

I believe the TT S2-6400 DVB-S/DVB-S2 card has two frontends that can be 
used simultaneously.

Regards,

Richard
  
Wolfgang Rohdewald Dec. 1, 2011, 7 p.m. UTC | #3
Am Freitag, 2. Dezember 2011, 07:48:20 schrieb Richard Scobie:
> I believe the TT S2-6400 DVB-S/DVB-S2 card has two frontends that can be 
> used simultaneously.

but is has only one frontend per adapter, so it has no multi-frontend

./adapter0:
insgesamt 0
crw-rw----+ 1 root video 212, 0 29. Nov 20:02 demux0
crw-rw----+ 1 root video 212, 1 29. Nov 20:02 dvr0
crw-rw----+ 1 root video 212, 3 29. Nov 20:02 frontend0
crw-rw----+ 1 root video 212, 2 29. Nov 20:02 net0

./adapter1:
insgesamt 0
crw-rw----+ 1 root video 212, 13 29. Nov 20:03 audio0
crw-rw----+ 1 root video 212,  4 29. Nov 20:03 demux0
crw-rw----+ 1 root video 212,  5 29. Nov 20:03 dvr0
crw-rw----+ 1 root video 212,  7 29. Nov 20:03 frontend0
crw-rw----+ 1 root video 212,  6 29. Nov 20:03 net0
crw-rw----+ 1 root video 212, 14 29. Nov 20:03 osd0
crw-rw----+ 1 root video 212, 12 29. Nov 20:03 video0


--
  
Hawes, Mark Jan. 8, 2012, 1:09 a.m. UTC | #4
Hi Lars,
I have got the sc plugin working with your hybrid patch v2 and
introduced the premium card and all is working well.
Are you planning any more revisions to the patch? Or at least a 1.7.22
version?
Thanks,
Mark. 

-----Original Message-----
From: Hawes, Mark 
Sent: Thursday, 1 December 2011 10:08 PM
To: 'VDR Mailing List'
Subject: RE: [vdr] [PATCH v2] multi-frontend-support for vdr 1.7.21

Hi Lars,
First reports on v2 of your multi-frontend patch with HVR 4000 card:
  - can switch between both frontends successfully and very stable with
repetitive tests 
  - timer behaviour as expected
  - switching response seems quicker than before
  - Streamdev and xineliboutput plugins compile OK. Xlo tested OK, will
look at Sd later
  - Have modified Rotor plugin to fit (maintaining personal version) and
all seems OK
  - Working through sc plugin changes to fit.
If I can get the sc plugin working I'll move across a sd premium card
into the mix and see how it behaves, watch this space ...       
While this is all good obviously things will no doubt change when Klaus
releases 2.x with the new multi-frontend adapter handling. However,
reading between the lines this may not be in the immediate future so an
interim workaround  for these cards is appreciated by me and I expect
others ...
Thanks and keep up the good work.
Mark.


-----Original Message-----
From: vdr-bounces@linuxtv.org [mailto:vdr-bounces@linuxtv.org] On Behalf
Of L. Hanisch
Sent: Thursday, 1 December 2011 10:50 AM
To: VDR Mailing List
Subject: [vdr] [PATCH v2] multi-frontend-support for vdr 1.7.21

Hi,

  Here's version 2 of my multi-frontend-patch. It's still "dirty", since
it changes the constructor of cDvbDevice which will break compilation of
some plugins. But I think it might be necessary to look at the relevant
plugins since they might need to react on frontend changes. I haven't
tested any of those plugins but will have a look at some that I'm using.
Maybe there have to be some virtual functions like
"BeforeFrontendSwitch" and "AfterFrontendSwitch" so the plugins are even
able to know about it.

  Assumption for this patch:
  All frontends within one adapter have to be used mutually exclusive.
All cards I know behave in this way. If there are cards with multiple
frontends which can be used simultaneously I'd like to hear about it.

  Whenever the dvb-api-changes are upstream (the ENUM_DELSYS thingy) I
think my patch can easily be converted to use that.

  I'm still working on this patch, it's not finished yet... :-)

  Have fun,

Lars.
  
L. Hanisch Jan. 8, 2012, 7:45 p.m. UTC | #5
Hi,

Am 08.01.2012 02:09, schrieb Hawes, Mark:
> Hi Lars,
> I have got the sc plugin working with your hybrid patch v2 and
> introduced the premium card and all is working well.
> Are you planning any more revisions to the patch? Or at least a 1.7.22
> version?

  Not really, because the driver changes introduced by Manu are on its way into linux-media. After that only one 
frontend will be left and new ioctls are there to switch between delivery systems.
  Rumours say Klaus is working on it for vdr 1.7.23... :-)

  But if you like, you can send me your changes. I'm curious about them.

Lars.

> Thanks,
> Mark.
>
> -----Original Message-----
> From: Hawes, Mark
> Sent: Thursday, 1 December 2011 10:08 PM
> To: 'VDR Mailing List'
> Subject: RE: [vdr] [PATCH v2] multi-frontend-support for vdr 1.7.21
>
> Hi Lars,
> First reports on v2 of your multi-frontend patch with HVR 4000 card:
>    - can switch between both frontends successfully and very stable with
> repetitive tests
>    - timer behaviour as expected
>    - switching response seems quicker than before
>    - Streamdev and xineliboutput plugins compile OK. Xlo tested OK, will
> look at Sd later
>    - Have modified Rotor plugin to fit (maintaining personal version) and
> all seems OK
>    - Working through sc plugin changes to fit.
> If I can get the sc plugin working I'll move across a sd premium card
> into the mix and see how it behaves, watch this space ...
> While this is all good obviously things will no doubt change when Klaus
> releases 2.x with the new multi-frontend adapter handling. However,
> reading between the lines this may not be in the immediate future so an
> interim workaround  for these cards is appreciated by me and I expect
> others ...
> Thanks and keep up the good work.
> Mark.
>
>
> -----Original Message-----
> From: vdr-bounces@linuxtv.org [mailto:vdr-bounces@linuxtv.org] On Behalf
> Of L. Hanisch
> Sent: Thursday, 1 December 2011 10:50 AM
> To: VDR Mailing List
> Subject: [vdr] [PATCH v2] multi-frontend-support for vdr 1.7.21
>
> Hi,
>
>    Here's version 2 of my multi-frontend-patch. It's still "dirty", since
> it changes the constructor of cDvbDevice which will break compilation of
> some plugins. But I think it might be necessary to look at the relevant
> plugins since they might need to react on frontend changes. I haven't
> tested any of those plugins but will have a look at some that I'm using.
> Maybe there have to be some virtual functions like
> "BeforeFrontendSwitch" and "AfterFrontendSwitch" so the plugins are even
> able to know about it.
>
>    Assumption for this patch:
>    All frontends within one adapter have to be used mutually exclusive.
> All cards I know behave in this way. If there are cards with multiple
> frontends which can be used simultaneously I'd like to hear about it.
>
>    Whenever the dvb-api-changes are upstream (the ENUM_DELSYS thingy) I
> think my patch can easily be converted to use that.
>
>    I'm still working on this patch, it's not finished yet... :-)
>
>    Have fun,
>
> Lars.
>
>
> _______________________________________________
> vdr mailing list
> vdr@linuxtv.org
> http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr
>
  
Klaus Schmidinger Jan. 8, 2012, 8:10 p.m. UTC | #6
On 08.01.2012 20:45, Lars Hanisch wrote:
> Hi,
>
> Am 08.01.2012 02:09, schrieb Hawes, Mark:
>> Hi Lars,
>> I have got the sc plugin working with your hybrid patch v2 and
>> introduced the premium card and all is working well.
>> Are you planning any more revisions to the patch? Or at least a 1.7.22
>> version?
>
> Not really, because the driver changes introduced by Manu are on its way into linux-media. After that only one frontend will be left and new ioctls are there to switch between delivery systems.
> Rumours say Klaus is working on it for vdr 1.7.23... :-)

Version 1.7.23 will contain multi frontend support with the new API.

Klaus
  
Udo Richter Jan. 8, 2012, 11:19 p.m. UTC | #7
Am 08.01.2012 21:10, schrieb Klaus Schmidinger:
> On 08.01.2012 20:45, Lars Hanisch wrote:
>> Not really, because the driver changes introduced by Manu are on its
>> way into linux-media. After that only one frontend will be left and
>> new ioctls are there to switch between delivery systems.
>> Rumours say Klaus is working on it for vdr 1.7.23... :-)
> 
> Version 1.7.23 will contain multi frontend support with the new API.

Will 1.7.23 require a kernel with the new API or will it be backwards
compatible?
Otherwise, s2apiwrapper ftw! ;)

Cheers,

Udo
  
Klaus Schmidinger Jan. 8, 2012, 11:33 p.m. UTC | #8
On 09.01.2012, at 00:19, Udo Richter <udo_richter@gmx.de> wrote:

> Am 08.01.2012 21:10, schrieb Klaus Schmidinger:
>> On 08.01.2012 20:45, Lars Hanisch wrote:
>>> Not really, because the driver changes introduced by Manu are on its
>>> way into linux-media. After that only one frontend will be left and
>>> new ioctls are there to switch between delivery systems.
>>> Rumours say Klaus is working on it for vdr 1.7.23... :-)
>> 
>> Version 1.7.23 will contain multi frontend support with the new API.
> 
> Will 1.7.23 require a kernel with the new API or will it be backwards
> compatible?

It will be backwards compatible.

Klaus
  

Patch

diff --git a/PLUGINS/src/dvbhddevice/dvbhdffdevice.c b/PLUGINS/src/dvbhddevice/dvbhdffdevice.c
index ff3f953..e7fb935 100644
--- a/PLUGINS/src/dvbhddevice/dvbhdffdevice.c
+++ b/PLUGINS/src/dvbhddevice/dvbhdffdevice.c
@@ -26,7 +26,8 @@ 
 int cDvbHdFfDevice::devHdffOffset = -1;
 
 cDvbHdFfDevice::cDvbHdFfDevice(int Adapter, int Frontend)
-:cDvbDevice(Adapter, Frontend)
+:cDvbDevice(Adapter)
+,frontend(Frontend)
 {
   spuDecoder = NULL;
   audioChannel = 0;
diff --git a/PLUGINS/src/dvbhddevice/dvbhdffdevice.h b/PLUGINS/src/dvbhddevice/dvbhdffdevice.h
index 4dcfb6a..62540da 100644
--- a/PLUGINS/src/dvbhddevice/dvbhdffdevice.h
+++ b/PLUGINS/src/dvbhddevice/dvbhdffdevice.h
@@ -17,12 +17,13 @@ 
 
 class cDvbHdFfDevice : public cDvbDevice {
 private:
+  int frontend;
   int fd_osd, fd_audio, fd_video;
 protected:
   virtual void MakePrimaryDevice(bool On);
 public:
   static bool Probe(int Adapter, int Frontend);
-  cDvbHdFfDevice(int Adapter, int Frontend);
+  cDvbHdFfDevice(int Adapter, int Frontend = 0);
   virtual ~cDvbHdFfDevice();
   virtual bool HasDecoder(void) const;
 
diff --git a/PLUGINS/src/dvbsddevice/dvbsdffdevice.c b/PLUGINS/src/dvbsddevice/dvbsdffdevice.c
index 17f842b..68031b5 100644
--- a/PLUGINS/src/dvbsddevice/dvbsdffdevice.c
+++ b/PLUGINS/src/dvbsddevice/dvbsdffdevice.c
@@ -24,7 +24,8 @@ 
 int cDvbSdFfDevice::devVideoOffset = -1;
 
 cDvbSdFfDevice::cDvbSdFfDevice(int Adapter, int Frontend, bool OutputOnly)
-:cDvbDevice(Adapter, Frontend)
+:cDvbDevice(Adapter)
+,frontend(Frontend)
 {
   spuDecoder = NULL;
   digitalAudio = false;
diff --git a/PLUGINS/src/dvbsddevice/dvbsdffdevice.h b/PLUGINS/src/dvbsddevice/dvbsdffdevice.h
index bd74cde..c060859 100644
--- a/PLUGINS/src/dvbsddevice/dvbsdffdevice.h
+++ b/PLUGINS/src/dvbsddevice/dvbsdffdevice.h
@@ -16,6 +16,7 @@ 
 
 class cDvbSdFfDevice : public cDvbDevice {
 private:
+  int frontend;
   int fd_osd, fd_audio, fd_video, fd_stc;
   bool outputOnly;
 protected:
diff --git a/dvbci.c b/dvbci.c
index 5289bbd..5db673e 100644
--- a/dvbci.c
+++ b/dvbci.c
@@ -10,15 +10,32 @@ 
 #include "dvbci.h"
 #include <linux/dvb/ca.h>
 #include <sys/ioctl.h>
-#include "device.h"
+#include "dvbdevice.h"
 
 // --- cDvbCiAdapter ---------------------------------------------------------
 
-cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
+cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Ca)
 {
   device = Device;
   SetDescription("CI adapter on device %d", device->DeviceNumber());
   fd = Fd;
+  adapter = Adapter;
+  ca = Ca;
+  gotCaps = false;
+  GetCaps();
+}
+
+cDvbCiAdapter::~cDvbCiAdapter()
+{
+  Cancel(3);
+  CloseCa();
+}
+
+void cDvbCiAdapter::GetCaps(void)
+{
+  if (gotCaps || (fd < 0))
+     return;
+  gotCaps = true;
   ca_caps_t Caps;
   if (ioctl(fd, CA_GET_CAP, &Caps) == 0) {
      if ((Caps.slot_type & CA_CI_LINK) != 0) {
@@ -38,13 +55,31 @@  cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd)
      esyslog("ERROR: can't get CA capabilities on device %d", device->DeviceNumber());
 }
 
-cDvbCiAdapter::~cDvbCiAdapter()
+bool cDvbCiAdapter::OpenCa(void)
 {
-  Cancel(3);
+  if (fd >= 0)
+     return true;
+  fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, ca, O_RDWR);
+  if (fd < 0) {
+     esyslog("ERROR: can't open ca %d/%d", adapter, ca);
+     return false;
+     }
+  GetCaps();
+  return true;
+}
+
+void cDvbCiAdapter::CloseCa(void)
+{
+  if (fd < 0)
+     return;
+  close(fd);
+  fd = -1;
 }
 
 int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
 {
+  if (fd < 0)
+     return 0;
   if (Buffer && MaxLength > 0) {
      struct pollfd pfd[1];
      pfd[0].fd = fd;
@@ -61,6 +96,8 @@  int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength)
 
 void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
 {
+  if (fd < 0)
+     return;
   if (Buffer && Length > 0) {
      if (safe_write(fd, Buffer, Length) != Length)
         esyslog("ERROR: can't write to CI adapter on device %d: %m", device->DeviceNumber());
@@ -69,6 +106,8 @@  void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length)
 
 bool cDvbCiAdapter::Reset(int Slot)
 {
+  if (fd < 0)
+     return false;
   if (ioctl(fd, CA_RESET, 1 << Slot) != -1)
      return true;
   else
@@ -78,6 +117,8 @@  bool cDvbCiAdapter::Reset(int Slot)
 
 eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot)
 {
+  if (fd < 0)
+     return msNone;
   ca_slot_info_t sinfo;
   sinfo.num = Slot;
   if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) {
@@ -99,10 +140,10 @@  bool cDvbCiAdapter::Assign(cDevice *Device, bool Query)
   return true;
 }
 
-cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd)
+cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Ca)
 {
   // TODO check whether a CI is actually present?
   if (Device)
-     return new cDvbCiAdapter(Device, Fd);
+     return new cDvbCiAdapter(Device, Fd, Adapter, Ca);
   return NULL;
 }
diff --git a/dvbci.h b/dvbci.h
index adbe40d..0776a7b 100644
--- a/dvbci.h
+++ b/dvbci.h
@@ -16,16 +16,24 @@  class cDvbCiAdapter : public cCiAdapter {
 private:
   cDevice *device;
   int fd;
+  int adapter;
+  int ca;
+  bool gotCaps;
+
+  void GetCaps(void);
+
 protected:
   virtual int Read(uint8_t *Buffer, int MaxLength);
   virtual void Write(const uint8_t *Buffer, int Length);
   virtual bool Reset(int Slot);
   virtual eModuleStatus ModuleStatus(int Slot);
   virtual bool Assign(cDevice *Device, bool Query = false);
-  cDvbCiAdapter(cDevice *Device, int Fd);
+  cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Ca);
 public:
   virtual ~cDvbCiAdapter();
-  static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd);
+  bool OpenCa(void);
+  void CloseCa(void);
+  static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Ca = -1);
   };
 
 #endif //__DVBCI_H
diff --git a/dvbdevice.c b/dvbdevice.c
index a97f274..0e0acd0 100644
--- a/dvbdevice.c
+++ b/dvbdevice.c
@@ -276,8 +276,9 @@  private:
   bool GetFrontendStatus(fe_status_t &Status) const;
   bool SetFrontend(void);
   virtual void Action(void);
+
 public:
-  cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType);
+  cDvbTuner(int Device, int Adapter, int Frontend, fe_delivery_system FrontendType);
   virtual ~cDvbTuner();
   const cChannel *GetTransponder(void) const { return &channel; }
   uint32_t SubsystemId(void) const { return subsystemId; }
@@ -286,12 +287,15 @@  public:
   bool Locked(int TimeoutMs = 0);
   int GetSignalStrength(void) const;
   int GetSignalQuality(void) const;
+
+  bool OpenFrontend(void);
+  bool CloseFrontend(void);
   };
 
-cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType)
+cDvbTuner::cDvbTuner(int Device, int Adapter, int Frontend, fe_delivery_system FrontendType)
 {
   device = Device;
-  fd_frontend = Fd_Frontend;
+  fd_frontend = -1;
   adapter = Adapter;
   frontend = Frontend;
   frontendType = FrontendType;
@@ -301,10 +305,7 @@  cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_
   lastTimeoutReport = 0;
   diseqcCommands = NULL;
   tunerStatus = tsIdle;
-  if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2)
-     CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power
   SetDescription("tuner on frontend %d/%d", adapter, frontend);
-  Start();
 }
 
 cDvbTuner::~cDvbTuner()
@@ -313,6 +314,7 @@  cDvbTuner::~cDvbTuner()
   newSet.Broadcast();
   locked.Broadcast();
   Cancel(3);
+  CloseFrontend();
 }
 
 bool cDvbTuner::IsTunedTo(const cChannel *Channel) const
@@ -349,6 +351,8 @@  bool cDvbTuner::Locked(int TimeoutMs)
 
 void cDvbTuner::ClearEventQueue(void) const
 {
+  if (fd_frontend < 0)
+     return;
   cPoller Poller(fd_frontend);
   if (Poller.Poll(TUNER_POLL_TIMEOUT)) {
      dvb_frontend_event Event;
@@ -359,6 +363,8 @@  void cDvbTuner::ClearEventQueue(void) const
 
 bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const
 {
+  if (fd_frontend < 0)
+     return false;
   ClearEventQueue();
   while (1) {
         if (ioctl(fd_frontend, FE_READ_STATUS, &Status) != -1)
@@ -374,6 +380,8 @@  bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const
 
 int cDvbTuner::GetSignalStrength(void) const
 {
+  if (fd_frontend < 0)
+     return -1;
   ClearEventQueue();
   uint16_t Signal;
   while (1) {
@@ -401,6 +409,8 @@  int cDvbTuner::GetSignalStrength(void) const
 
 int cDvbTuner::GetSignalQuality(void) const
 {
+  if (fd_frontend < 0)
+     return -1;
   fe_status_t Status;
   if (GetFrontendStatus(Status)) {
      // Actually one would expect these checks to be done from FE_HAS_SIGNAL to FE_HAS_LOCK, but some drivers (like the stb0899) are broken, so FE_HAS_LOCK is the only one that (hopefully) is generally reliable...
@@ -484,6 +494,8 @@  static unsigned int FrequencyToHz(unsigned int f)
 
 bool cDvbTuner::SetFrontend(void)
 {
+  if (fd_frontend < 0)
+     return false;
 #define MAXFRONTENDCMDS 16
 #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\
                        Frontend[CmdSeq.num].u.data = (d);\
@@ -643,9 +655,11 @@  void cDvbTuner::Action(void)
   bool LostLock = false;
   fe_status_t Status = (fe_status_t)0;
   while (Running()) {
-        fe_status_t NewStatus;
-        if (GetFrontendStatus(NewStatus))
-           Status = NewStatus;
+        if (fd_frontend >= 0) {
+           fe_status_t NewStatus;
+           if (GetFrontendStatus(NewStatus))
+              Status = NewStatus;
+           }
         cMutexLock MutexLock(&mutex);
         switch (tunerStatus) {
           case tsIdle:
@@ -698,6 +712,37 @@  void cDvbTuner::Action(void)
         }
 }
 
+bool cDvbTuner::OpenFrontend(void)
+{
+  if (fd_frontend >= 0)
+     return true;
+  isyslog("opening frontend %d/%d", adapter, frontend);
+  cMutexLock MutexLock(&mutex);
+  fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK);
+  if (fd_frontend < 0)
+     return false;
+  if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2)
+#ifdef LNB_SHARING_VERSION
+     if (lnbSendSignals)
+#endif
+     CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power
+  Start();
+  return true;
+}
+
+bool cDvbTuner::CloseFrontend(void)
+{
+  if (fd_frontend < 0)
+     return true;
+  isyslog("closing frontend %d/%d", adapter, frontend);
+  cMutexLock MutexLock(&mutex);
+  tunerStatus = tsIdle;
+  newSet.Broadcast();
+  close(fd_frontend);
+  fd_frontend = -1;
+  return true;
+}
+
 // --- cDvbSourceParam -------------------------------------------------------
 
 class cDvbSourceParam : public cSourceParam {
@@ -778,77 +823,112 @@  const char *DeliverySystems[] = {
   NULL
   };
 
-cDvbDevice::cDvbDevice(int Adapter, int Frontend)
+cDvbDevice::cDvbDevice(int Adapter)
 {
+  numFrontends = 0;
+  currentFrontend = 0;
   adapter = Adapter;
-  frontend = Frontend;
-  ciAdapter = NULL;
-  dvbTuner = NULL;
-  frontendType = SYS_UNDEFINED;
   numProvidedSystems = 0;
-
-  // 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);
-  if (fd_ca >= 0)
-     ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca);
-
+  int fd_frontend = -1;
   // The DVR device (will be opened and closed as needed):
-
   fd_dvr = -1;
 
-  // We only check the devices that must be present - the others will be checked before accessing them://XXX
-
-  if (fd_frontend >= 0) {
-     if (ioctl(fd_frontend, FE_GET_INFO, &frontendInfo) >= 0) {
-        switch (frontendInfo.type) {
-          case FE_QPSK: frontendType = (frontendInfo.caps & FE_CAN_2G_MODULATION) ? SYS_DVBS2 : SYS_DVBS; break;
-          case FE_OFDM: frontendType = SYS_DVBT; break;
-          case FE_QAM:  frontendType = SYS_DVBC_ANNEX_AC; break;
-          case FE_ATSC: frontendType = SYS_ATSC; break;
-          default: esyslog("ERROR: unknown frontend type %d on frontend %d/%d", frontendInfo.type, adapter, frontend);
-          }
-        }
-     else
-        LOG_ERROR;
-     if (frontendType != SYS_UNDEFINED) {
-        numProvidedSystems++;
-        if (frontendType == SYS_DVBS2)
-           numProvidedSystems++;
-        char Modulations[64];
-        char *p = Modulations;
-        if (frontendInfo.caps & FE_CAN_QPSK)    { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QPSK, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_QAM_16)  { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_16, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_QAM_32)  { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_32, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_QAM_64)  { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_64, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_QAM_128) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_128, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_QAM_256) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_256, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_8VSB)    { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(VSB_8, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_16VSB)   { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(VSB_16, ModulationValues)); }
-        if (frontendInfo.caps & FE_CAN_TURBO_FEC){numProvidedSystems++; p += sprintf(p, ",%s", "TURBO_FEC"); }
-        if (p != Modulations)
-           p = Modulations + 1; // skips first ','
-        else
-           p = (char *)"unknown modulations";
-        isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, DeliverySystems[frontendType], p, frontendInfo.name);
-        dvbTuner = new cDvbTuner(CardIndex() + 1, fd_frontend, adapter, frontend, frontendType);
-        }
+  for (int f = 0; (numFrontends < MAXDVBFRONTENDS) && Exists(adapter, f); f++) {
+      frontends[numFrontends].frontend = f;
+      frontends[numFrontends].demux = frontends[numFrontends].frontend;
+      frontends[numFrontends].dvr = frontends[numFrontends].frontend;
+      frontends[numFrontends].ca = frontends[numFrontends].frontend;
+      frontends[numFrontends].frontendType = SYS_UNDEFINED;
+      frontends[numFrontends].ciAdapter = NULL;
+      frontends[numFrontends].dvbTuner = NULL;
+
+      // Devices that are present on all card types:
+
+      isyslog("probing frontend %d/%d", adapter, f);
+      fd_frontend = DvbOpen(DEV_DVB_FRONTEND, adapter, frontends[numFrontends].frontend, O_RDWR | O_NONBLOCK);
+
+      if (fd_frontend >= 0) {
+         // Look for the right devices
+         while ((frontends[numFrontends].demux >= 0) && !Exists(DEV_DVB_DEMUX, adapter, frontends[numFrontends].demux))
+               frontends[numFrontends].demux--;
+         if (frontends[numFrontends].demux < 0) {
+            frontends[numFrontends].demux = frontends[numFrontends].frontend;
+            esyslog("frontend %d/%d has no demux device", adapter, frontends[numFrontends].frontend);
+            }
+         else if (frontends[numFrontends].demux != frontends[numFrontends].frontend)
+            isyslog("frontend %d/%d will use demux%d", adapter, frontends[numFrontends].frontend, frontends[numFrontends].demux);
+
+         while ((frontends[numFrontends].dvr >= 0) && !Exists(DEV_DVB_DVR, adapter, frontends[numFrontends].dvr))
+               frontends[numFrontends].dvr--;
+         if (frontends[numFrontends].dvr < 0) {
+            frontends[numFrontends].dvr = frontends[numFrontends].frontend;
+            esyslog("frontend %d/%d has no dvr device", adapter, frontends[numFrontends].frontend);
+            }
+         else if (frontends[numFrontends].dvr != frontends[numFrontends].frontend)
+            isyslog("frontend %d/%d will use dvr%d", adapter, frontends[numFrontends].frontend, frontends[numFrontends].dvr);
+
+         while ((frontends[numFrontends].ca >= 0) && !Exists(DEV_DVB_CA, adapter, frontends[numFrontends].ca))
+               frontends[numFrontends].ca--;
+         if ((frontends[numFrontends].ca >= 0) && (frontends[numFrontends].ca != frontends[numFrontends].frontend))
+            isyslog("frontend %d/%d will use ca%d", adapter, frontends[numFrontends].frontend, frontends[numFrontends].ca);
+
+         if (ioctl(fd_frontend, FE_GET_INFO, &frontends[numFrontends].frontendInfo) >= 0) {
+            switch (frontends[numFrontends].frontendInfo.type) {
+              case FE_QPSK: frontends[numFrontends].frontendType = (frontends[numFrontends].frontendInfo.caps & FE_CAN_2G_MODULATION) ? SYS_DVBS2 : SYS_DVBS; break;
+              case FE_OFDM: frontends[numFrontends].frontendType = SYS_DVBT; break;
+              case FE_QAM:  frontends[numFrontends].frontendType = SYS_DVBC_ANNEX_AC; break;
+              case FE_ATSC: frontends[numFrontends].frontendType = SYS_ATSC; break;
+              default: esyslog("ERROR: unknown frontend type %d on frontend %d/%d", frontends[numFrontends].frontendInfo.type, adapter, frontends[numFrontends].frontend);
+              }
+            }
+         else
+            LOG_ERROR;
+         if (frontends[numFrontends].frontendType != SYS_UNDEFINED) {
+            numProvidedSystems++;
+            if (frontends[numFrontends].frontendType == SYS_DVBS2)
+               numProvidedSystems++;
+            char Modulations[64];
+            char *p = Modulations;
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_QPSK)    { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QPSK, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_QAM_16)  { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_16, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_QAM_32)  { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_32, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_QAM_64)  { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_64, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_QAM_128) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_128, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_QAM_256) { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(QAM_256, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_8VSB)    { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(VSB_8, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_16VSB)   { numProvidedSystems++; p += sprintf(p, ",%s", MapToUserString(VSB_16, ModulationValues)); }
+            if (frontends[numFrontends].frontendInfo.caps & FE_CAN_TURBO_FEC){numProvidedSystems++; p += sprintf(p, ",%s", "TURBO_FEC"); }
+            if (p != Modulations)
+               p = Modulations + 1; // skips first ','
+            else
+               p = (char *)"unknown modulations";
+            isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontends[numFrontends].frontend, DeliverySystems[frontends[numFrontends].frontendType], p, frontends[numFrontends].frontendInfo.name);
+            frontends[numFrontends].dvbTuner = new cDvbTuner(CardIndex() + 1, adapter, frontends[numFrontends].frontend, frontends[numFrontends].frontendType);
+            if (frontends[numFrontends].ca >= 0)
+               frontends[numFrontends].ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, -1, adapter, frontends[numFrontends].ca);
+            numFrontends++;
+            }
+         close (fd_frontend);
+         }
+      else
+         esyslog("ERROR: can't open DVB device %d/%d", adapter, frontends[numFrontends].frontend);
+      }
+  if (numFrontends > 0) {
+     if (frontends[currentFrontend].ciAdapter)
+        frontends[currentFrontend].ciAdapter->OpenCa();
+     if (frontends[currentFrontend].dvbTuner)
+        frontends[currentFrontend].dvbTuner->OpenFrontend();
      }
-  else
-     esyslog("ERROR: can't open DVB device %d/%d", adapter, frontend);
-
   StartSectionHandler();
 }
 
 cDvbDevice::~cDvbDevice()
 {
   StopSectionHandler();
-  delete dvbTuner;
-  delete ciAdapter;
+  for (int f = 0; f < numFrontends; f++) {
+      delete frontends[f].dvbTuner;
+      delete frontends[f].ciAdapter;
+      }
   // We're not explicitly closing any device files here, since this sometimes
   // caused segfaults. Besides, the program is about to terminate anyway...
 }
@@ -869,7 +949,12 @@  int cDvbDevice::DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, b
 
 bool cDvbDevice::Exists(int Adapter, int Frontend)
 {
-  cString FileName = DvbName(DEV_DVB_FRONTEND, Adapter, Frontend);
+  return Exists(DEV_DVB_FRONTEND, Adapter, Frontend);
+}
+
+bool cDvbDevice::Exists(const char *Name, int Adapter, int Frontend)
+{
+  cString FileName = DvbName(Name, Adapter, Frontend);
   if (access(FileName, F_OK) == 0) {
      int f = open(FileName, O_RDONLY);
      if (f >= 0) {
@@ -884,16 +969,16 @@  bool cDvbDevice::Exists(int Adapter, int Frontend)
   return false;
 }
 
-bool cDvbDevice::Probe(int Adapter, int Frontend)
+bool cDvbDevice::Probe(int Adapter)
 {
-  cString FileName = DvbName(DEV_DVB_FRONTEND, Adapter, Frontend);
-  dsyslog("probing %s", *FileName);
+  cString adapterName = cString::sprintf("%s%d", DEV_DVB_ADAPTER, Adapter);
+  dsyslog("probing %s", *adapterName);
   for (cDvbDeviceProbe *dp = DvbDeviceProbes.First(); dp; dp = DvbDeviceProbes.Next(dp)) {
-      if (dp->Probe(Adapter, Frontend))
+      if (dp->Probe(Adapter, 0))
          return true; // a plugin has created the actual device
       }
   dsyslog("creating cDvbDevice");
-  new cDvbDevice(Adapter, Frontend); // it's a "budget" device
+  new cDvbDevice(Adapter); // it's a "budget" device
   return true;
 }
 
@@ -906,23 +991,18 @@  bool cDvbDevice::Initialize(void)
   int Checked = 0;
   int Found = 0;
   for (int Adapter = 0; ; Adapter++) {
-      for (int Frontend = 0; ; Frontend++) {
-          if (Exists(Adapter, Frontend)) {
-             if (Checked++ < MAXDVBDEVICES) {
-                if (UseDevice(NextCardIndex())) {
-                   if (Probe(Adapter, Frontend))
-                      Found++;
-                   }
-                else
-                   NextCardIndex(1); // skips this one
-                }
-             }
-          else if (Frontend == 0)
-             goto LastAdapter;
-          else
-             goto NextAdapter;
-          }
-      NextAdapter: ;
+      if (Exists(DEV_DVB_FRONTEND, Adapter, 0)) {
+         if (Checked++ < MAXDVBDEVICES) {
+            if (UseDevice(NextCardIndex())) {
+               if (Probe(Adapter))
+                  Found++;
+               }
+            else
+               NextCardIndex(1); // skips this one
+            }
+         }
+         else
+            goto LastAdapter;
       }
 LastAdapter:
   NextCardIndex(MAXDVBDEVICES - Checked); // skips the rest
@@ -935,14 +1015,14 @@  LastAdapter:
 
 bool cDvbDevice::Ready(void)
 {
-  if (ciAdapter)
-     return ciAdapter->Ready();
+  if (frontends[currentFrontend].ciAdapter)
+     return frontends[currentFrontend].ciAdapter->Ready();
   return true;
 }
 
 bool cDvbDevice::HasCi(void)
 {
-  return ciAdapter;
+  return frontends[currentFrontend].ciAdapter;
 }
 
 bool cDvbDevice::SetPid(cPidHandle *Handle, int Type, bool On)
@@ -952,7 +1032,7 @@  bool cDvbDevice::SetPid(cPidHandle *Handle, int Type, bool On)
      memset(&pesFilterParams, 0, sizeof(pesFilterParams));
      if (On) {
         if (Handle->handle < 0) {
-           Handle->handle = DvbOpen(DEV_DVB_DEMUX, adapter, frontend, O_RDWR | O_NONBLOCK, true);
+           Handle->handle = DvbOpen(DEV_DVB_DEMUX, adapter, frontends[currentFrontend].demux, O_RDWR | O_NONBLOCK, true);
            if (Handle->handle < 0) {
               LOG_ERROR;
               return false;
@@ -987,7 +1067,7 @@  bool cDvbDevice::SetPid(cPidHandle *Handle, int Type, bool On)
 
 int cDvbDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
 {
-  cString FileName = DvbName(DEV_DVB_DEMUX, adapter, frontend);
+  cString FileName = DvbName(DEV_DVB_DEMUX, adapter, frontends[currentFrontend].demux);
   int f = open(FileName, O_RDWR | O_NONBLOCK);
   if (f >= 0) {
      dmx_sct_filter_params sctFilterParams;
@@ -1014,32 +1094,43 @@  void cDvbDevice::CloseFilter(int Handle)
   close(Handle);
 }
 
-bool cDvbDevice::ProvidesSource(int Source) const
+int cDvbDevice::GetFrontend(int Source) const
 {
   int type = Source & cSource::st_Mask;
-  return type == cSource::stNone
-      || type == cSource::stAtsc  && (frontendType == SYS_ATSC)
-      || type == cSource::stCable && (frontendType == SYS_DVBC_ANNEX_AC || frontendType == SYS_DVBC_ANNEX_B)
-      || type == cSource::stSat   && (frontendType == SYS_DVBS || frontendType == SYS_DVBS2)
-      || type == cSource::stTerr  && (frontendType == SYS_DVBT);
+  if (type == cSource::stNone)
+     return 0; // can this happen?
+  for (int f = 0; f < numFrontends; f++) {
+      if (type == cSource::stAtsc  && (frontends[f].frontendType == SYS_ATSC)
+          || type == cSource::stCable && (frontends[f].frontendType == SYS_DVBC_ANNEX_AC || frontends[f].frontendType == SYS_DVBC_ANNEX_B)
+          || type == cSource::stSat   && (frontends[f].frontendType == SYS_DVBS || frontends[f].frontendType == SYS_DVBS2)
+          || type == cSource::stTerr  && (frontends[f].frontendType == SYS_DVBT))
+         return f;
+      }
+  return -1;
+}
+
+bool cDvbDevice::ProvidesSource(int Source) const
+{
+  return (GetFrontend(Source) >= 0);
 }
 
 bool cDvbDevice::ProvidesTransponder(const cChannel *Channel) const
 {
-  if (!ProvidesSource(Channel->Source()))
+  int f = GetFrontend(Channel->Source());
+  if (f < 0)
      return false; // doesn't provide source
   cDvbTransponderParameters dtp(Channel->Parameters());
-  if (dtp.System() == SYS_DVBS2 && frontendType == SYS_DVBS ||
-     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 (dtp.System() == SYS_DVBS2 && frontends[f].frontendType == SYS_DVBS ||
+     dtp.Modulation() == QPSK     && !(frontends[f].frontendInfo.caps & FE_CAN_QPSK) ||
+     dtp.Modulation() == QAM_16   && !(frontends[f].frontendInfo.caps & FE_CAN_QAM_16) ||
+     dtp.Modulation() == QAM_32   && !(frontends[f].frontendInfo.caps & FE_CAN_QAM_32) ||
+     dtp.Modulation() == QAM_64   && !(frontends[f].frontendInfo.caps & FE_CAN_QAM_64) ||
+     dtp.Modulation() == QAM_128  && !(frontends[f].frontendInfo.caps & FE_CAN_QAM_128) ||
+     dtp.Modulation() == QAM_256  && !(frontends[f].frontendInfo.caps & FE_CAN_QAM_256) ||
+     dtp.Modulation() == QAM_AUTO && !(frontends[f].frontendInfo.caps & FE_CAN_QAM_AUTO) ||
+     dtp.Modulation() == VSB_8    && !(frontends[f].frontendInfo.caps & FE_CAN_8VSB) ||
+     dtp.Modulation() == VSB_16   && !(frontends[f].frontendInfo.caps & FE_CAN_16VSB) ||
+     dtp.Modulation() == PSK_8    && !(frontends[f].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
      return false; // requires modulation system which frontend doesn't provide
   if (!cSource::IsSat(Channel->Source()) ||
      !Setup.DiSEqC || Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization()))
@@ -1053,10 +1144,10 @@  bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
   bool hasPriority = Priority < 0 || Priority > this->Priority();
   bool needsDetachReceivers = false;
 
-  if (dvbTuner && ProvidesTransponder(Channel)) {
+  if (frontends[currentFrontend].dvbTuner && ProvidesTransponder(Channel)) {
      result = hasPriority;
      if (Priority >= 0 && Receiving(true)) {
-        if (dvbTuner->IsTunedTo(Channel)) {
+        if (frontends[currentFrontend].dvbTuner->IsTunedTo(Channel)) {
            if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) {
               if (CamSlot() && Channel->Ca() >= CA_ENCRYPTED_MIN) {
                  if (CamSlot()->CanDecrypt(Channel))
@@ -1083,7 +1174,7 @@  bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne
 
 bool cDvbDevice::ProvidesEIT(void) const
 {
-  return dvbTuner != NULL;
+  return frontends[currentFrontend].dvbTuner != NULL;
 }
 
 int cDvbDevice::NumProvidedSystems(void) const
@@ -1093,34 +1184,64 @@  int cDvbDevice::NumProvidedSystems(void) const
 
 int cDvbDevice::SignalStrength(void) const
 {
-  return dvbTuner ? dvbTuner->GetSignalStrength() : -1;
+  return frontends[currentFrontend].dvbTuner ? frontends[currentFrontend].dvbTuner->GetSignalStrength() : -1;
 }
 
 int cDvbDevice::SignalQuality(void) const
 {
-  return dvbTuner ? dvbTuner->GetSignalQuality() : -1;
+  return frontends[currentFrontend].dvbTuner ? frontends[currentFrontend].dvbTuner->GetSignalQuality() : -1;
 }
 
 const cChannel *cDvbDevice::GetCurrentlyTunedTransponder(void) const
 {
-  return dvbTuner ? dvbTuner->GetTransponder() : NULL;
+  return frontends[currentFrontend].dvbTuner ? frontends[currentFrontend].dvbTuner->GetTransponder() : NULL;
 }
 
 bool cDvbDevice::IsTunedToTransponder(const cChannel *Channel)
 {
-  return dvbTuner ? dvbTuner->IsTunedTo(Channel) : false;
+  return frontends[currentFrontend].dvbTuner ? frontends[currentFrontend].dvbTuner->IsTunedTo(Channel) : false;
 }
 
 bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
 {
-  if (dvbTuner)
-     dvbTuner->Set(Channel);
+  if (numFrontends > 1) {
+     int f = GetFrontend(Channel->Source());
+     if (f < 0)
+        return false;
+     if (currentFrontend != f) {
+        isyslog("switching frontend on adapter %d from %d to %d", adapter, frontends[currentFrontend].frontend, frontends[f].frontend);
+        StopSectionHandler();
+        if (frontends[currentFrontend].dvbTuner)
+           frontends[currentFrontend].dvbTuner->CloseFrontend();
+        if (frontends[currentFrontend].ciAdapter)
+           frontends[currentFrontend].ciAdapter->CloseCa();
+        currentFrontend = f;
+        if (frontends[currentFrontend].ciAdapter) {
+           frontends[currentFrontend].ciAdapter->OpenCa();
+           bool ready = false;
+           for (time_t t0 = time(NULL); time(NULL) - t0 < 10; ) {
+               if (frontends[currentFrontend].ciAdapter->Ready()) {
+                  ready = true;
+                  break;
+                  }
+               cCondWait::SleepMs(100);
+               }
+           if (!ready)
+              return false;
+           }
+        if (frontends[currentFrontend].dvbTuner)
+           frontends[currentFrontend].dvbTuner->OpenFrontend();
+        StartSectionHandler();
+        }
+     }
+  if (frontends[currentFrontend].dvbTuner)
+     frontends[currentFrontend].dvbTuner->Set(Channel);
   return true;
 }
 
 bool cDvbDevice::HasLock(int TimeoutMs)
 {
-  return dvbTuner ? dvbTuner->Locked(TimeoutMs) : false;
+  return frontends[currentFrontend].dvbTuner ? frontends[currentFrontend].dvbTuner->Locked(TimeoutMs) : false;
 }
 
 void cDvbDevice::SetTransferModeForDolbyDigital(int Mode)
@@ -1131,7 +1252,7 @@  void cDvbDevice::SetTransferModeForDolbyDigital(int Mode)
 bool cDvbDevice::OpenDvr(void)
 {
   CloseDvr();
-  fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontend, O_RDONLY | O_NONBLOCK, true);
+  fd_dvr = DvbOpen(DEV_DVB_DVR, adapter, frontends[currentFrontend].dvr, O_RDONLY | O_NONBLOCK, true);
   if (fd_dvr >= 0)
      tsBuffer = new cTSBuffer(fd_dvr, MEGABYTE(2), CardIndex() + 1);
   return fd_dvr >= 0;
diff --git a/dvbdevice.h b/dvbdevice.h
index e1842b7..1de9a0e 100644
--- a/dvbdevice.h
+++ b/dvbdevice.h
@@ -20,6 +20,8 @@ 
 #endif
 
 #define MAXDVBDEVICES  8
+#define MAXDVBFRONTENDS 8
+#define MULTI_FRONTEND_PATCH
 
 #define DEV_VIDEO         "/dev/video"
 #define DEV_DVB_ADAPTER   "/dev/dvb/adapter"
@@ -97,18 +99,33 @@  public:
   bool Parse(const char *s);
   };
 
+class cDvbCiAdapter;
 class cDvbTuner;
 
 /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API.
 
+struct tDvbFrontend {
+  int frontend;
+  int demux;
+  int dvr;
+  int ca;
+
+  dvb_frontend_info frontendInfo;
+  fe_delivery_system frontendType;
+
+  cDvbCiAdapter *ciAdapter;
+  cDvbTuner *dvbTuner;
+  };
+
 class cDvbDevice : public cDevice {
-protected:
+public:
   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);
+  static bool Exists(const char *Name, int Adapter, int Frontend);
          ///< Checks whether the given adapter/frontend exists.
-  static bool Probe(int Adapter, int Frontend);
+  static bool Probe(int Adapter);
          ///< Probes for existing DVB devices.
 public:
   static bool Initialize(void);
@@ -116,26 +133,20 @@  public:
          ///< Must be called before accessing any DVB functions.
          ///< \return True if any devices are available.
 protected:
-  int adapter, frontend;
+  int adapter;
 private:
-  dvb_frontend_info frontendInfo;
+  tDvbFrontend frontends[MAXDVBFRONTENDS];
+  int numFrontends;
+  int currentFrontend;
+  int GetFrontend(int Source) const;
+
   int numProvidedSystems;
-  fe_delivery_system frontendType;
-  int fd_dvr, fd_ca;
+  int fd_dvr;
 public:
-  cDvbDevice(int Adapter, int Frontend);
+  cDvbDevice(int Adapter);
   virtual ~cDvbDevice();
   virtual bool Ready(void);
 
-// Common Interface facilities:
-
-private:
-  cCiAdapter *ciAdapter;
-
-// Channel facilities
-
-private:
-  cDvbTuner *dvbTuner;
 public:
   virtual bool ProvidesSource(int Source) const;
   virtual bool ProvidesTransponder(const cChannel *Channel) const;