[vdr] 01/01: Version 2.3.3

Tobias Grimm tiber-guest at moszumanska.debian.org
Wed Dec 27 13:02:20 UTC 2017


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

tiber-guest pushed a commit to annotated tag vdr-2.3.3
in repository vdr.

commit ff4c0a818042e001d5a13587a9db8774c1b1a7f1
Author: Klaus Schmidinger <Klaus (dot) Schmidinger (at) tvdr (dot) de>
Date:   Tue Mar 28 11:10:00 2017 +0200

    Version 2.3.3
    
    VDR developer version 2.3.3 is now available at
    
           ftp://ftp.tvdr.de/vdr/Developer/vdr-2.3.3.tar.bz2
    
    A 'diff' against the previous version is available at
    
           ftp://ftp.tvdr.de/vdr/Developer/vdr-2.3.2-2.3.3.diff
    
    MD5 checksums:
    
    73182b570bcf5a67ab56f7734e479631  vdr-2.3.3.tar.bz2
    112c2057dbd7e86c31f8227f61cfd2a6  vdr-2.3.2-2.3.3.diff
    
    WARNING:
    ========
    
    This is a *developer* version. Even though *I* use it in my productive
    environment, I strongly recommend that you only use it under controlled
    conditions and for testing and debugging.
    
    From the HISTORY file:
    - Added 'S3W ABS-3A' to sources.conf (thanks to Frank Richter).
    - Fixed a possible deadlock in the recordings handler thread.
    - Updated the Russian OSD texts (thanks to Andrey Pridvorov).
    - Added a missing dependency to the Makefile to avoid error messages in the
      clean-plugins target (thanks to Tobias Grimm).
    - The channel/CAM relations (i.e. the information which CAM can decrypt a given
      channel) are now stored in the file 'cam.data' in the cache directory (suggested
      by Dietmar Spingler). This speeds up switching to encrypted channels after
      newly starting VDR, in case there is more than one CAM in the system.
    - Fixed a flaw in handling timeouts for encrypted channels.
    - The mechanism of trying different CAMs when switching to an encrypted channel is
      now only triggered if there acually is more than one CAM in the system.
    - Fixed updating the elapsed/remaining time in the progress display during fast
      forward/rewind.
    - Changed 'unsigned' to 'signed' in some places to avoid trouble with abs() in
      gcc6+ (reported by Derek Kelly).
    - CAMs that can handle multiple devices at the same time can now indicate this
      by creating the first cCamSlot as usual, and every other cCamSlot by giving
      it the first one as its "MasterSlot". To VDR this means that when searching
      for a CAM that can decrypt a particular channel, it only needs to ask the
      master CAM slot whether it is suitable for decrypting, and can skip all the
      other slots belonging to the same master. This can greatly speed up channel
      switching on systems with more than one CAM (that can handle multiple devices).
    - The LCARS skin now displays the master CAM's number when a device is tuned to
      an encrypted channel.
    - The Setup/CAM menu now only displays master CAMs.
    - Fixed setting the local machine's SVDRP host name (was overwritten if setup.conf
      contained an empty string).
    - PIDs can now be added to and deleted from a cReceiver while it is attached to
      a cDevice, without having to detach it first and re-attach it afterwards.
    - Implemented support for MTD ("Multi Transponder Decryption"). This allows a CAM
      that is capable of decrypting more than one channel ("Multi Channel Decryption")
      to decrypt channels from different transponders. See the remarks in mtd.h on
      what a derived cCamSlot class needs to do in order to activate MTD (thanks to
      Jasmin Jessich for writing the ddci2 plugin and for valuable input and help
      with testing and debugging).
    - The function cRingBufferLinear::Clear() can now be called safely from the
      reading thread, without additional locking.
    - Now stopping any ongoing recordings before stopping the plugins, to avoid
      a crash when stopping VDR while recording.
---
 CONTRIBUTORS |   10 +
 HISTORY      |   43 +++
 Makefile     |    6 +-
 ci.c         |  470 +++++++++++++++++++++------
 ci.h         |  107 +++++-
 config.c     |    4 +-
 config.h     |   10 +-
 device.c     |  146 +++++++--
 device.h     |    5 +-
 diseqc.c     |   10 +-
 diseqc.h     |    6 +-
 dvbdevice.c  |   10 +-
 menu.c       |   39 ++-
 mtd.c        |  341 +++++++++++++++++++
 mtd.h        |  188 +++++++++++
 po/ru_RU.po  | 1030 +++++++++++++++++++++++++++++-----------------------------
 receiver.c   |    9 +-
 recording.c  |   18 +-
 remux.c      |   24 +-
 remux.h      |   17 +-
 ringbuffer.c |    7 +-
 ringbuffer.h |    4 +-
 skinlcars.c  |    4 +-
 sources.conf |    1 +
 tools.h      |   18 +-
 vdr.5        |   16 +-
 vdr.c        |    9 +-
 27 files changed, 1832 insertions(+), 720 deletions(-)

diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index ca58ae8..dac5059 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -2478,6 +2478,8 @@ Tobias Grimm <tobias.grimm at e-tobi.net>
  numbers use a decimal point
  for adding dependency on 'i18n' to 'install-i18n' in the VDR Makefile
  for adding a manual page for 'svdrpsend'
+ for adding a missing dependency to the Makefile to avoid error messages in the
+ clean-plugins target
 
 Helge Lenz <h.lenz at gmx.de>
  for reporting a bug in setting the 'Delta' parameter when calling the shutdown
@@ -2848,6 +2850,7 @@ Derek Kelly <user.vdr at gmail.com>
  for suggesting to add ARGSDIR to the ONEDIR section of Make.config.template
  for suggesting to change the naming of "binary skip mode" to "adaptive skip mode"
  for suggesting to make the -u option also accept a numerical user id
+ for reporting a problem with abs() in gcc6+ when called with an unsigned variable
 
 Marcel Unbehaun <frostworks at gmx.de>
  for adding cRecordingInfo::GetEvent()
@@ -3170,6 +3173,7 @@ Oliver Schinagl <oliver at schinagl.nl>
 Andrey Pridvorov <ua0lnj at bk.ru>
  for reporting a problem with detecting frames in H.264 video, and pointing towards
  a better way of doing it
+ for updating the Russian OSD texts
 
 Jens Vogel <jens.vogel at akjv.de>
  for suggesting to make cPatPmtParser::ParsePmt() also recognize stream type 0x81
@@ -3380,6 +3384,7 @@ Dietmar Spingler <d_spingler at gmx.de>
  for suggesting to provide a way of using no DVB devices at all
  for suggesting that the -V and -h options should list the plugins in alphabetical order
  for suggesting to implement the setup option "Recording/Record key handling"
+ for suggesting to cache the channel/CAM relations in the file 'cam.data'
 
 Stefan Schallenberg <infos at nafets.de>
  for adding the functions IndexOf(), InsertUnique(), AppendUnique() and RemoveElement()
@@ -3400,6 +3405,8 @@ Dieter Ferdinand <dieter.ferdinand at gmx.de>
 Jasmin Jessich <jasmin at anw.at>
  for modifying the CAM API so that it is possible to implement CAMs that can be freely
  assigned to any devices
+ for writing the ddci2 plugin and for valuable input and help with testing and
+ debugging MTD support
 
 Martin Schirrmacher <schirrmie at gmail.com>
  for suggesting to provide a way for skin plugins to get informed about the currently
@@ -3459,3 +3466,6 @@ Aitugan Sarbassov <isarbassov at gmail.com>
 Sergey Chernyavskiy <glenvt18 at gmail.com>
  for reporting truncated date/time strings in the skins on multi-byte UTF-8
  for adding a short sleep to cTSBuffer::Action() to avoid high CPU usage
+
+Frank Richter <kulpstur at t-online.de>
+ for adding 'S3W ABS-3A' to sources.conf
diff --git a/HISTORY b/HISTORY
index 932b963..74d22fb 100644
--- a/HISTORY
+++ b/HISTORY
@@ -8881,3 +8881,46 @@ Video Disk Recorder Revision History
 - Added support for the systemd watchdog (thanks to Marc Perrudin),
 - Added a short sleep to cTSBuffer::Action() to avoid high CPU usage (thanks to
   Sergey Chernyavskiy).
+
+2017-03-28: Version 2.3.3
+
+- Added 'S3W ABS-3A' to sources.conf (thanks to Frank Richter).
+- Fixed a possible deadlock in the recordings handler thread.
+- Updated the Russian OSD texts (thanks to Andrey Pridvorov).
+- Added a missing dependency to the Makefile to avoid error messages in the
+  clean-plugins target (thanks to Tobias Grimm).
+- The channel/CAM relations (i.e. the information which CAM can decrypt a given
+  channel) are now stored in the file 'cam.data' in the cache directory (suggested
+  by Dietmar Spingler). This speeds up switching to encrypted channels after
+  newly starting VDR, in case there is more than one CAM in the system.
+- Fixed a flaw in handling timeouts for encrypted channels.
+- The mechanism of trying different CAMs when switching to an encrypted channel is
+  now only triggered if there acually is more than one CAM in the system.
+- Fixed updating the elapsed/remaining time in the progress display during fast
+  forward/rewind.
+- Changed 'unsigned' to 'signed' in some places to avoid trouble with abs() in
+  gcc6+ (reported by Derek Kelly).
+- CAMs that can handle multiple devices at the same time can now indicate this
+  by creating the first cCamSlot as usual, and every other cCamSlot by giving
+  it the first one as its "MasterSlot". To VDR this means that when searching
+  for a CAM that can decrypt a particular channel, it only needs to ask the
+  master CAM slot whether it is suitable for decrypting, and can skip all the
+  other slots belonging to the same master. This can greatly speed up channel
+  switching on systems with more than one CAM (that can handle multiple devices).
+- The LCARS skin now displays the master CAM's number when a device is tuned to
+  an encrypted channel.
+- The Setup/CAM menu now only displays master CAMs.
+- Fixed setting the local machine's SVDRP host name (was overwritten if setup.conf
+  contained an empty string).
+- PIDs can now be added to and deleted from a cReceiver while it is attached to
+  a cDevice, without having to detach it first and re-attach it afterwards.
+- Implemented support for MTD ("Multi Transponder Decryption"). This allows a CAM
+  that is capable of decrypting more than one channel ("Multi Channel Decryption")
+  to decrypt channels from different transponders. See the remarks in mtd.h on
+  what a derived cCamSlot class needs to do in order to activate MTD (thanks to
+  Jasmin Jessich for writing the ddci2 plugin and for valuable input and help
+  with testing and debugging).
+- The function cRingBufferLinear::Clear() can now be called safely from the
+  reading thread, without additional locking.
+- Now stopping any ongoing recordings before stopping the plugins, to avoid
+  a crash when stopping VDR while recording.
diff --git a/Makefile b/Makefile
index a0d78b2..d6bdc31 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: Makefile 4.1 2016/12/22 13:18:32 kls Exp $
+# $Id: Makefile 4.3 2017/02/20 09:22:47 kls Exp $
 
 .DELETE_ON_ERROR:
 
@@ -69,7 +69,7 @@ SILIB    = $(LSIDIR)/libsi.a
 
 OBJS = args.o audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o\
        dvbplayer.o dvbspu.o dvbsubtitle.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\
-       lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o positioner.o\
+       lirc.o menu.o menuitems.o mtd.o nit.o osdbase.o osd.o pat.o player.o plugin.o positioner.o\
        receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o shutdown.o\
        skinclassic.o skinlcars.o skins.o skinsttng.o sourceparams.o sources.o spu.o status.o svdrp.o themes.o thread.o\
        timers.o tools.o transfer.o vdr.o videodir.o
@@ -250,7 +250,7 @@ plugins: include-dir vdr.pc
 	   fi;\
 	if [ -n "$$failed" ] ; then echo; echo "*** failed plugins:$$failed"; echo; exit 1; fi
 
-clean-plugins:
+clean-plugins: vdr.pc
 	@for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do $(MAKE) --no-print-directory -C "$(PLUGINDIR)/src/$$i" VDRDIR="$(CWD)" clean; done
 	@-rm -f $(PLUGINDIR)/lib/lib*-*.so.$(APIVERSION)
 
diff --git a/ci.c b/ci.c
index 606875b..71623d8 100644
--- a/ci.c
+++ b/ci.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ci.c 4.3 2016/12/23 14:00:45 kls Exp $
+ * $Id: ci.c 4.9 2017/03/25 14:09:23 kls Exp $
  */
 
 #include "ci.h"
@@ -13,11 +13,13 @@
 #include <malloc.h>
 #include <netinet/in.h>
 #include <poll.h>
+#include <stdio.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <time.h>
 #include <unistd.h>
 #include "device.h"
+#include "mtd.h"
 #include "pat.h"
 #include "receiver.h"
 #include "remux.h"
@@ -117,11 +119,10 @@ private:
   cVector<int> emmPids;
   uchar buffer[2048]; // 11 bit length, max. 2048 byte
   uchar *bufp;
+  uchar mtdCatBuffer[TS_SIZE]; // TODO: handle multi packet CATs!
   int length;
   void AddEmmPid(int Pid);
   void DelEmmPids(void);
-protected:
-  virtual void Activate(bool On);
 public:
   cCaPidReceiver(void);
   virtual ~cCaPidReceiver() { Detach(); }
@@ -155,14 +156,10 @@ void cCaPidReceiver::DelEmmPids(void)
   emmPids.Clear();
 }
 
-void cCaPidReceiver::Activate(bool On)
-{
-  catVersion = -1; // can be done independent of 'On'
-}
-
 void cCaPidReceiver::Receive(const uchar *Data, int Length)
 {
   if (TsPid(Data) == CATPID) {
+     cMtdCamSlot *MtdCamSlot = dynamic_cast<cMtdCamSlot *>(Device()->CamSlot());
      const uchar *p = NULL;
      if (TsPayloadStart(Data)) {
         if (Data[5] == SI::TableIdCAT) {
@@ -172,6 +169,8 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
               if (v != catVersion) {
                  if (Data[11] == 0 && Data[12] == 0) { // section number, last section number
                     if (length > TS_SIZE - 8) {
+                       if (MtdCamSlot)
+                          esyslog("ERROR: need to implement multi packet CAT handling for MTD!");
                        int n = TS_SIZE - 13;
                        memcpy(buffer, Data + 13, n);
                        bufp = buffer + n;
@@ -186,6 +185,8 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
                     dsyslog("multi table CAT section - unhandled!");
                  catVersion = v;
                  }
+              else if (MtdCamSlot)
+                 MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE);
               }
            }
         }
@@ -207,32 +208,32 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
            }
         }
      if (p) {
-        int OldCatVersion = catVersion; // must preserve the current version number
-        cDevice *AttachedDevice = Device();
-        if (AttachedDevice)
-           AttachedDevice->Detach(this);
         DelEmmPids();
         for (int i = 0; i < length - 4; i++) { // -4 = checksum
             if (p[i] == 0x09) {
                int CaId = int(p[i + 2] << 8) | p[i + 3];
-               int EmmPid = int(((p[i + 4] & 0x1F) << 8)) | p[i + 5];
+               int EmmPid = Peek13(p + i + 4);
                AddEmmPid(EmmPid);
+               if (MtdCamSlot)
+                  MtdMapPid(const_cast<uchar *>(p + i + 4), MtdCamSlot->MtdMapper());
                switch (CaId >> 8) {
                  case 0x01: for (int j = i + 7; j < p[i + 1] + 2; j += 4) {
-                                EmmPid = (int(p[j] & 0x0F) << 8) | p[j + 1];
+                                EmmPid = Peek13(p + j);
                                 AddEmmPid(EmmPid);
+                                if (MtdCamSlot)
+                                   MtdMapPid(const_cast<uchar *>(p + j), MtdCamSlot->MtdMapper());
                                 }
                             break;
                  }
                i += p[i + 1] + 2 - 1; // -1 to compensate for the loop increment
                }
             }
-        if (AttachedDevice)
-           AttachedDevice->AttachReceiver(this);
-        catVersion = OldCatVersion;
         p = NULL;
         bufp = 0;
         length = 0;
+        memcpy(mtdCatBuffer, Data, TS_SIZE);
+        if (MtdCamSlot)
+           MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE);
         }
      }
 }
@@ -279,7 +280,12 @@ void cCaActivationReceiver::Receive(const uchar *Data, int Length)
      else if (Now - lastScrambledTime > UNSCRAMBLE_TIME) {
         dsyslog("CAM %d: activated!", camSlot->SlotNumber());
         Skins.QueueMessage(mtInfo, tr("CAM activated!"));
+        cDevice *d = Device();
         Detach();
+        if (d) {
+           if (cCamSlot *s = d->CamSlot())
+              s->CancelActivation(); // this will delete *this* object, so no more code referencing *this* after this call!
+           }
         }
      }
 }
@@ -770,6 +776,7 @@ public:
   void SetListManagement(uint8_t ListManagement);
   uint8_t ListManagement(void) { return capmt.Get(0); }
   void AddPid(int Pid, uint8_t StreamType);
+  void MtdMapPids(cMtdMapper *MtdMapper);
   };
 
 cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds)
@@ -830,6 +837,84 @@ void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data)
      esyslog("ERROR: adding CA descriptor without Pid!");
 }
 
+static int MtdMapCaDescriptor(uchar *p, cMtdMapper *MtdMapper)
+{
+  // See pat.c: cCaDescriptor::cCaDescriptor() for the layout of the data!
+  if (*p == SI::CaDescriptorTag) {
+     int l = *++p;
+     if (l >= 4) {
+        MtdMapPid(p + 3, MtdMapper);
+        return l + 2;
+        }
+     else
+        esyslog("ERROR: wrong length (%d) in MtdMapCaDescriptor()", l);
+     }
+  else
+     esyslog("ERROR: wrong tag (%d) in MtdMapCaDescriptor()", *p);
+  return -1;
+}
+
+static int MtdMapCaDescriptors(uchar *p, cMtdMapper *MtdMapper)
+{
+  int Length = p[0] * 256 + p[1];
+  if (Length >= 3) {
+     p += 3;
+     int m = Length - 1;
+     while (m > 0) {
+           int l = MtdMapCaDescriptor(p, MtdMapper);
+           if (l > 0) {
+              p += l;
+              m -= l;
+              }
+           }
+     }
+  return Length + 2;
+}
+
+static int MtdMapStream(uchar *p, cMtdMapper *MtdMapper)
+{
+  // See ci.c: cCiCaPmt::AddPid() for the layout of the data!
+  MtdMapPid(p + 1, MtdMapper);
+  int l = MtdMapCaDescriptors(p + 3, MtdMapper);
+  if (l > 0)
+     return l + 3;
+  return -1;
+}
+
+static int MtdMapStreams(uchar *p, cMtdMapper *MtdMapper, int Length)
+{
+  int m = Length;
+  while (m >= 5) {
+        int l = MtdMapStream(p, MtdMapper);
+        if (l > 0) {
+           p += l;
+           m -= l;
+           }
+        else
+           break;
+        }
+  return Length;
+}
+
+void cCiCaPmt::MtdMapPids(cMtdMapper *MtdMapper)
+{
+  uchar *p = capmt.Data();
+  int m = capmt.Length();
+  if (m >= 3) {
+     MtdMapSid(p + 1, MtdMapper);
+     p += 4;
+     m -= 4;
+     if (m >= 2) {
+        int l = MtdMapCaDescriptors(p, MtdMapper);
+        if (l >= 0) {
+           p += l;
+           m -= l;
+           MtdMapStreams(p, MtdMapper, m);
+           }
+        }
+     }
+}
+
 // --- cCiConditionalAccessSupport -------------------------------------------
 
 // CA Enable Ids:
@@ -910,8 +995,12 @@ void cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data)
        case AOT_CA_PMT_REPLY: {
             dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", Tc()->CamSlot()->SlotNumber(), SessionId());
             if (!repliesToQuery) {
-               dsyslog("CAM %d: replies to QUERY - multi channel decryption possible", Tc()->CamSlot()->SlotNumber());
+               dsyslog("CAM %d: replies to QUERY - multi channel decryption (MCD) possible", Tc()->CamSlot()->SlotNumber());
                repliesToQuery = true;
+               if (Tc()->CamSlot()->MtdAvailable()) {
+                  dsyslog("CAM %d: supports multi transponder decryption (MTD)", Tc()->CamSlot()->SlotNumber());
+                  Tc()->CamSlot()->MtdActivate(true);
+                  }
                }
             state = 5; // got ca pmt reply
             int l = 0;
@@ -1663,6 +1752,14 @@ public:
     programNumber = ProgramNumber;
     modified = false;
   }
+  bool Active(void)
+  {
+    for (cCiCaPidData *p = pidList.First(); p; p = pidList.Next(p)) {
+        if (p->active)
+           return true;
+        }
+    return false;
+  }
   };
 
 // --- cCiAdapter ------------------------------------------------------------
@@ -1730,55 +1827,79 @@ void cCiAdapter::Action(void)
 #define MODULE_CHECK_INTERVAL 500 // ms
 #define MODULE_RESET_TIMEOUT    2 // s
 
-cCamSlot::cCamSlot(cCiAdapter *CiAdapter, bool ReceiveCaPids)
+cCamSlot::cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData, cCamSlot *MasterSlot)
 {
   ciAdapter = CiAdapter;
+  masterSlot = MasterSlot;
   assignedDevice = NULL;
-  caPidReceiver = ReceiveCaPids ? new cCaPidReceiver : NULL;
+  caPidReceiver = WantsTsData ? new cCaPidReceiver : NULL;
   caActivationReceiver = NULL;
   slotIndex = -1;
+  mtdAvailable = false;
+  mtdHandler = NULL;
   lastModuleStatus = msReset; // avoids initial reset log message
   resetTime = 0;
   resendPmt = false;
-  source = transponder = 0;
   for (int i = 0; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) // tc[0] is not used, but initialized anyway
       tc[i] = NULL;
-  CamSlots.Add(this);
-  slotNumber = Index() + 1;
-  if (ciAdapter)
+  if (MasterSlot)
+     slotNumber = MasterSlot->SlotNumber();
+  if (ciAdapter) {
+     CamSlots.Add(this);
+     slotNumber = Index() + 1;
      ciAdapter->AddCamSlot(this);
-  Reset();
+     Reset();
+     }
 }
 
 cCamSlot::~cCamSlot()
 {
-  if (assignedDevice)
-     assignedDevice->SetCamSlot(NULL);
+  Assign(NULL);
   delete caPidReceiver;
   delete caActivationReceiver;
   CamSlots.Del(this, false);
   DeleteAllConnections();
+  delete mtdHandler;
+}
+
+cCamSlot *cCamSlot::MtdSpawn(void)
+{
+  cMutexLock MutexLock(&mutex);
+  if (mtdHandler)
+     return mtdHandler->GetMtdCamSlot(this);
+  return this;
 }
 
 bool cCamSlot::Assign(cDevice *Device, bool Query)
 {
   cMutexLock MutexLock(&mutex);
+  if (Device == assignedDevice)
+     return true;
   if (ciAdapter) {
+     int OldDeviceNumber = 0;
+     if (assignedDevice && !Query) {
+        OldDeviceNumber = assignedDevice->DeviceNumber() + 1;
+        if (caPidReceiver)
+           assignedDevice->Detach(caPidReceiver);
+        assignedDevice->SetCamSlot(NULL);
+        assignedDevice = NULL;
+        }
      if (ciAdapter->Assign(Device, true)) {
-        if (!Device && assignedDevice)
-           assignedDevice->SetCamSlot(NULL);
-        if (!Query || !Device) {
+        if (!Query) {
            StopDecrypting();
-           source = transponder = 0;
            if (ciAdapter->Assign(Device)) {
-              assignedDevice = Device;
               if (Device) {
                  Device->SetCamSlot(this);
-                 dsyslog("CAM %d: assigned to device %d", slotNumber, Device->DeviceNumber() + 1);
+                 assignedDevice = Device;
+                 if (caPidReceiver) {
+                    caPidReceiver->Reset();
+                    Device->AttachReceiver(caPidReceiver);
+                    }
+                 dsyslog("CAM %d: assigned to device %d", MasterSlotNumber(), Device->DeviceNumber() + 1);
                  }
               else {
                  CancelActivation();
-                 dsyslog("CAM %d: unassigned", slotNumber);
+                 dsyslog("CAM %d: unassigned from device %d", MasterSlotNumber(), OldDeviceNumber);
                  }
               }
            else
@@ -1790,6 +1911,16 @@ bool cCamSlot::Assign(cDevice *Device, bool Query)
   return false;
 }
 
+bool cCamSlot::Devices(cVector<int> &CardIndexes)
+{
+  cMutexLock MutexLock(&mutex);
+  if (mtdHandler)
+     return mtdHandler->Devices(CardIndexes);
+  if (assignedDevice)
+     CardIndexes.Append(assignedDevice->CardIndex());
+  return CardIndexes.Size() > 0;
+}
+
 void cCamSlot::NewConnection(void)
 {
   cMutexLock MutexLock(&mutex);
@@ -1815,8 +1946,6 @@ void cCamSlot::DeleteAllConnections(void)
 void cCamSlot::Process(cTPDU *TPDU)
 {
   cMutexLock MutexLock(&mutex);
-  if (caActivationReceiver && !caActivationReceiver->IsAttached())
-     CancelActivation();
   if (TPDU) {
      int n = TPDU->Tcid();
      if (1 <= n && n <= MAX_CONNECTIONS_PER_CAM_SLOT) {
@@ -1827,9 +1956,9 @@ void cCamSlot::Process(cTPDU *TPDU)
   for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) {
       if (tc[i]) {
          if (!tc[i]->Process()) {
-           Reset();
-           return;
-           }
+            Reset();
+            return;
+            }
          }
       }
   if (moduleCheckTimer.TimedOut()) {
@@ -1839,6 +1968,7 @@ void cCamSlot::Process(cTPDU *TPDU)
           case msNone:
                dbgprotocol("Slot %d: no module present\n", slotNumber);
                isyslog("CAM %d: no module present", slotNumber);
+               MtdActivate(false);
                DeleteAllConnections();
                CancelActivation();
                break;
@@ -1855,7 +1985,7 @@ void cCamSlot::Process(cTPDU *TPDU)
                dbgprotocol("Slot %d: module ready\n", slotNumber);
                isyslog("CAM %d: module ready", slotNumber);
                NewConnection();
-               resendPmt = caProgramList.Count() > 0;
+               resendPmt = true;
                break;
           default:
                esyslog("ERROR: unknown module status %d (%s)", ms, __FUNCTION__);
@@ -1864,8 +1994,14 @@ void cCamSlot::Process(cTPDU *TPDU)
         }
      moduleCheckTimer.Set(MODULE_CHECK_INTERVAL);
      }
-  if (resendPmt)
-     SendCaPmt(CPCI_OK_DESCRAMBLING);
+  if (resendPmt && Ready()) {
+     if (mtdHandler) {
+        mtdHandler->StartDecrypting();
+        resendPmt = false;
+        }
+     else if (caProgramList.Count())
+        StartDecrypting();
+     }
   processed.Broadcast();
 }
 
@@ -1925,12 +2061,18 @@ void cCamSlot::StartActivation(void)
 void cCamSlot::CancelActivation(void)
 {
   cMutexLock MutexLock(&mutex);
-  delete caActivationReceiver;
-  caActivationReceiver = NULL;
+  if (mtdHandler)
+     mtdHandler->CancelActivation();
+  else {
+     delete caActivationReceiver;
+     caActivationReceiver = NULL;
+     }
 }
 
 bool cCamSlot::IsActivating(void)
 {
+  if (mtdHandler)
+     return mtdHandler->IsActivating();
   return caActivationReceiver;
 }
 
@@ -2004,67 +2146,115 @@ cCiEnquiry *cCamSlot::GetEnquiry(void)
   return NULL;
 }
 
-void cCamSlot::SendCaPmt(uint8_t CmdId)
+cCiCaPmtList::~cCiCaPmtList()
+{
+  for (int i = 0; i < caPmts.Size(); i++)
+      delete caPmts[i];
+}
+
+cCiCaPmt *cCiCaPmtList::Add(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds)
+{
+  cCiCaPmt *p = new cCiCaPmt(CmdId, Source, Transponder, ProgramNumber, CaSystemIds);
+  caPmts.Append(p);
+  return p;
+}
+
+void cCiCaPmtList::Del(cCiCaPmt *CaPmt)
+{
+  if (caPmts.RemoveElement(CaPmt))
+     delete CaPmt;
+}
+
+bool cCamSlot::RepliesToQuery(void)
 {
   cMutexLock MutexLock(&mutex);
   cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
-  if (cas) {
-     const int *CaSystemIds = cas->GetCaSystemIds();
-     if (CaSystemIds && *CaSystemIds) {
-        if (caProgramList.Count()) {
-           if (caPidReceiver && caPidReceiver->HasCaPids()) {
-              if (cDevice *d = Device())
-                 d->Detach(caPidReceiver);
-              }
-           for (int Loop = 1; Loop <= 2; Loop++) {
-               for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
-                   if (p->modified || resendPmt) {
-                      bool Active = false;
-                      cCiCaPmt CaPmt(CmdId, source, transponder, p->programNumber, CaSystemIds);
-                      for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
-                          if (q->active) {
-                             CaPmt.AddPid(q->pid, q->streamType);
-                             Active = true;
-                             }
-                          }
-                      if ((Loop == 1) != Active) { // first remove, then add
-                         if (caPidReceiver) {
-                            int CaPids[MAXRECEIVEPIDS + 1];
-                            if (GetCaPids(source, transponder, p->programNumber, CaSystemIds, MAXRECEIVEPIDS + 1, CaPids) > 0) {
-                               if (Loop == 1)
-                                  caPidReceiver->DelPids(CaPids);
-                               else
-                                  caPidReceiver->AddPids(CaPids);
-                               }
-                            }
-                         if (cas->RepliesToQuery())
-                            CaPmt.SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE);
-                         if (Active || cas->RepliesToQuery())
-                            cas->SendPMT(&CaPmt);
-                         p->modified = false;
-                         }
-                      }
+  return cas && cas->RepliesToQuery();
+}
+
+void cCamSlot::BuildCaPmts(uint8_t CmdId, cCiCaPmtList &CaPmtList, cMtdMapper *MtdMapper)
+{
+  cMutexLock MutexLock(&mutex);
+  CaPmtList.caPmts.Clear();
+  const int *CaSystemIds = GetCaSystemIds();
+  if (CaSystemIds && *CaSystemIds) {
+     if (caProgramList.Count()) {
+        for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
+            if (p->modified || resendPmt) {
+               bool Active = p->Active();
+               cCiCaPmt *CaPmt = CaPmtList.Add(Active ? CmdId : CPCI_NOT_SELECTED, source, transponder, p->programNumber, CaSystemIds);
+               for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
+                   if (q->active)
+                      CaPmt->AddPid(q->pid, q->streamType);
                    }
+               if (caPidReceiver) {
+                  int CaPids[MAXRECEIVEPIDS + 1];
+                  if (GetCaPids(source, transponder, p->programNumber, CaSystemIds, MAXRECEIVEPIDS + 1, CaPids) > 0) {
+                     if (Active)
+                        caPidReceiver->AddPids(CaPids);
+                     else
+                        caPidReceiver->DelPids(CaPids);
+                     }
+                  }
+               if (RepliesToQuery())
+                  CaPmt->SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE);
+               if (MtdMapper)
+                  CaPmt->MtdMapPids(MtdMapper);
+               p->modified = false;
                }
-           if (caPidReceiver && caPidReceiver->HasCaPids()) {
-              if (cDevice *d = Device())
-                 d->AttachReceiver(caPidReceiver);
-              }
-           resendPmt = false;
-           }
-        else {
-           cCiCaPmt CaPmt(CmdId, 0, 0, 0, NULL);
-           cas->SendPMT(&CaPmt);
-           if (caPidReceiver) {
-              if (cDevice *d = Device())
-                 d->Detach(caPidReceiver);
-              caPidReceiver->Reset();
-              }
+            }
+        }
+     else if (CmdId == CPCI_NOT_SELECTED)
+        CaPmtList.Add(CmdId, 0, 0, 0, NULL);
+     }
+}
+
+void cCamSlot::SendCaPmts(cCiCaPmtList &CaPmtList)
+{
+  cMutexLock MutexLock(&mutex);
+  cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
+  if (cas) {
+     for (int i = 0; i < CaPmtList.caPmts.Size(); i++)
+         cas->SendPMT(CaPmtList.caPmts[i]);
+     }
+  resendPmt = false;
+}
+
+void cCamSlot::SendCaPmt(uint8_t CmdId)
+{
+  cMutexLock MutexLock(&mutex);
+  cCiCaPmtList CaPmtList;
+  BuildCaPmts(CmdId, CaPmtList);
+  SendCaPmts(CaPmtList);
+}
+
+void cCamSlot::MtdEnable(void)
+{
+  mtdAvailable = true;
+}
+
+void cCamSlot::MtdActivate(bool On)
+{
+  if (McdAvailable() && MtdAvailable()) {
+     if (On) {
+        if (!mtdHandler) {
+           dsyslog("CAM %d: activating MTD support", SlotNumber());
+           mtdHandler = new cMtdHandler;
            }
         }
+     else if (mtdHandler) {
+        dsyslog("CAM %d: deactivating MTD support", SlotNumber());
+        delete mtdHandler;
+        mtdHandler = NULL;
+        }
      }
 }
 
+int cCamSlot::MtdPutData(uchar *Data, int Count)
+{
+  return mtdHandler->Put(Data, Count);
+}
+
 const int *cCamSlot::GetCaSystemIds(void)
 {
   cMutexLock MutexLock(&mutex);
@@ -2074,6 +2264,8 @@ const int *cCamSlot::GetCaSystemIds(void)
 
 int cCamSlot::Priority(void)
 {
+  if (mtdHandler)
+     return mtdHandler->Priority();
   cDevice *d = Device();
   return d ? d->Priority() : IDLEPRIORITY;
 }
@@ -2123,7 +2315,7 @@ void cCamSlot::SetPid(int Pid, bool Active)
                 }
              return;
              }
-         }
+          }
       }
 }
 
@@ -2152,7 +2344,7 @@ void cCamSlot::AddChannel(const cChannel *Channel)
 
 #define QUERY_REPLY_WAIT  100 // ms to wait between checks for a reply
 
-bool cCamSlot::CanDecrypt(const cChannel *Channel)
+bool cCamSlot::CanDecrypt(const cChannel *Channel, cMtdMapper *MtdMapper)
 {
   if (Channel->Ca() < CA_ENCRYPTED_MIN)
      return true; // channel not encrypted
@@ -2170,6 +2362,8 @@ bool cCamSlot::CanDecrypt(const cChannel *Channel)
          CaPmt.AddPid(*Dpid, STREAM_TYPE_PRIVATE);
      for (const int *Spid = Channel->Spids(); *Spid; Spid++)
          CaPmt.AddPid(*Spid, STREAM_TYPE_PRIVATE);
+     if (MtdMapper)
+        CaPmt.MtdMapPids(MtdMapper);
      cas->SendPMT(&CaPmt);
      cTimeMs Timeout(QUERY_REPLY_TIMEOUT);
      do {
@@ -2196,13 +2390,16 @@ void cCamSlot::StopDecrypting(void)
   cMutexLock MutexLock(&mutex);
   if (caProgramList.Count()) {
      caProgramList.Clear();
-     SendCaPmt(CPCI_NOT_SELECTED);
+     if (!dynamic_cast<cMtdCamSlot *>(this))
+        SendCaPmt(CPCI_NOT_SELECTED);
      }
 }
 
 bool cCamSlot::IsDecrypting(void)
 {
   cMutexLock MutexLock(&mutex);
+  if (mtdHandler)
+     return mtdHandler->IsDecrypting();
   if (caProgramList.Count()) {
      for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
          if (p->modified)
@@ -2226,10 +2423,21 @@ uchar *cCamSlot::Decrypt(uchar *Data, int &Count)
 
 cCamSlots CamSlots;
 
+int cCamSlots::NumReadyMasterSlots(void)
+{
+  int n = 0;
+  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
+      if (CamSlot->IsMasterSlot() && CamSlot->ModuleStatus() == msReady)
+         n++;
+      }
+  return n;
+}
+
 bool cCamSlots::WaitForAllCamSlotsReady(int Timeout)
 {
+  bool ready = true;
   for (time_t t0 = time(NULL); time(NULL) - t0 < Timeout; ) {
-      bool ready = true;
+      ready = true;
       for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
           if (!CamSlot->Ready()) {
              ready = false;
@@ -2237,9 +2445,11 @@ bool cCamSlots::WaitForAllCamSlotsReady(int Timeout)
              }
           }
       if (ready)
-         return true;
+         break;
       }
-  return false;
+  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot))
+      dsyslog("CAM %d: %sready, %s", CamSlot->SlotNumber(), CamSlot->Ready() ? "" : "not ", CamSlot->IsMasterSlot() ? *cString::sprintf("master (%s)", CamSlot->GetCamName() ? CamSlot->GetCamName() : "empty") : *cString::sprintf("slave of CAM %d", CamSlot->MasterSlotNumber()));
+  return ready;
 }
 
 // --- cChannelCamRelation ---------------------------------------------------
@@ -2317,6 +2527,7 @@ void cChannelCamRelation::ClrDecrypt(int CamSlotNumber)
 
 // --- cChannelCamRelations --------------------------------------------------
 
+#define MAX_CAM_NUMBER 32
 #define CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL 3600 // seconds between cleanups
 
 cChannelCamRelations ChannelCamRelations;
@@ -2414,3 +2625,60 @@ void cChannelCamRelations::ClrDecrypt(tChannelID ChannelID, int CamSlotNumber)
   if (ccr)
      ccr->ClrDecrypt(CamSlotNumber);
 }
+
+void cChannelCamRelations::Load(const char *FileName)
+{
+  cMutexLock MutexLock(&mutex);
+  fileName = FileName;
+  if (access(fileName, R_OK) == 0) {
+     dsyslog("loading %s", *fileName);
+     if (FILE *f = fopen(fileName, "r")) {
+        cReadLine ReadLine;
+        char *s;
+        while ((s = ReadLine.Read(f)) != NULL) {
+              if (char *p = strchr(s, ' ')) {
+                 *p = 0;
+                 if (*++p) {
+                    tChannelID ChannelID = tChannelID::FromString(s);
+                    if (ChannelID.Valid()) {
+                       char *q;
+                       char *strtok_next;
+                       while ((q = strtok_r(p, " ", &strtok_next)) != NULL) {
+                             int CamSlotNumber = atoi(q);
+                             if (CamSlotNumber >= 1 && CamSlotNumber <= MAX_CAM_NUMBER)
+                                SetDecrypt(ChannelID, CamSlotNumber);
+                             p = NULL;
+                             }
+                       }
+                    }
+                 }
+              }
+        fclose(f);
+        }
+     else
+        LOG_ERROR_STR(*fileName);
+     }
+}
+
+void cChannelCamRelations::Save(void)
+{
+  cMutexLock MutexLock(&mutex);
+  dsyslog("saving %s", *fileName);
+  cSafeFile f(fileName);
+  if (f.Open()) {
+     for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) {
+         if (ccr->ChannelID().Valid()) {
+            cString s;
+            for (int i = 1; i <= MAX_CAM_NUMBER; i++) {
+                if (ccr->CamDecrypt(i))
+                   s = cString::sprintf("%s%s%d", *s ? *s : "", *s ? " " : "", i);
+                }
+            if (*s)
+               fprintf(f, "%s %s\n", *ccr->ChannelID().ToString(), *s);
+            }
+         }
+     f.Close();
+     }
+  else
+     LOG_ERROR_STR(*fileName);
+}
diff --git a/ci.h b/ci.h
index a420015..0e8253d 100644
--- a/ci.h
+++ b/ci.h
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ci.h 4.0 2015/01/31 14:36:41 kls Exp $
+ * $Id: ci.h 4.5 2017/03/23 14:23:33 kls Exp $
  */
 
 #ifndef __CI_H
@@ -13,10 +13,11 @@
 #include <stdint.h>
 #include <stdio.h>
 #include "channels.h"
+#include "ringbuffer.h"
 #include "thread.h"
 #include "tools.h"
 
-#define MAX_CAM_SLOTS_PER_ADAPTER     8 // maximum possible value is 255
+#define MAX_CAM_SLOTS_PER_ADAPTER    16 // maximum possible value is 255 (same value as MAXDEVICES!)
 #define MAX_CONNECTIONS_PER_CAM_SLOT  8 // maximum possible value is 254
 #define CAM_READ_TIMEOUT  50 // ms
 
@@ -124,14 +125,28 @@ class cCiSession;
 class cCiCaProgramData;
 class cCaPidReceiver;
 class cCaActivationReceiver;
+class cMtdHandler;
+class cMtdMapper;
+class cMtdCamSlot;
+class cCiCaPmt;
+
+struct cCiCaPmtList {
+  cVector<cCiCaPmt *> caPmts;
+  ~cCiCaPmtList();
+  cCiCaPmt *Add(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds);
+  void Del(cCiCaPmt *CaPmt);
+  };
 
 class cCamSlot : public cListObject {
   friend class cCiAdapter;
   friend class cCiTransportConnection;
+  friend class cCiConditionalAccessSupport;
+  friend class cMtdCamSlot;
 private:
   cMutex mutex;
   cCondVar processed;
   cCiAdapter *ciAdapter;
+  cCamSlot *masterSlot;
   cDevice *assignedDevice;
   cCaPidReceiver *caPidReceiver;
   cCaActivationReceiver *caActivationReceiver;
@@ -145,23 +160,70 @@ private:
   int source;
   int transponder;
   cList<cCiCaProgramData> caProgramList;
-  const int *GetCaSystemIds(void);
-  void SendCaPmt(uint8_t CmdId);
+  bool mtdAvailable;
+  cMtdHandler *mtdHandler;
   void NewConnection(void);
   void DeleteAllConnections(void);
   void Process(cTPDU *TPDU = NULL);
   void Write(cTPDU *TPDU);
   cCiSession *GetSessionByResourceId(uint32_t ResourceId);
+  void MtdActivate(bool On);
+       ///< Activates (On == true) or deactivates (On == false) MTD.
+protected:
+  virtual const int *GetCaSystemIds(void);
+  virtual void SendCaPmt(uint8_t CmdId);
+  virtual bool RepliesToQuery(void);
+       ///< Returns true if the CAM in this slot replies to queries and thus
+       ///< supports MCD ("Multi Channel Decryption").
+  void BuildCaPmts(uint8_t CmdId, cCiCaPmtList &CaPmtList, cMtdMapper *MtdMapper = NULL);
+       ///< Generates all CA_PMTs with the given CmdId and stores them in the given CaPmtList.
+       ///< If MtdMapper is given, all SIDs and PIDs will be mapped accordingly.
+  void SendCaPmts(cCiCaPmtList &CaPmtList);
+       ///< Sends the given list of CA_PMTs to the CAM.
+  void MtdEnable(void);
+       ///< Enables MTD support for this CAM. Note that actual MTD operation also
+       ///< requires a CAM that supports MCD ("Multi Channel Decryption").
+  int MtdPutData(uchar *Data, int Count);
+       ///< Sends at most Count bytes of the given Data to the individual MTD CAM slots
+       ///< that are using this CAM. Data must point to the beginning of a TS packet.
+       ///< Returns the number of bytes actually processed.
 public:
-  cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData = false);
+  bool McdAvailable(void) { return RepliesToQuery(); }
+       ///< Returns true if this CAM supports MCD ("Multi Channel Decyption").
+  bool MtdAvailable(void) { return mtdAvailable; }
+       ///< Returns true if this CAM supports MTD ("Multi Transponder Decryption").
+  bool MtdActive(void) { return mtdHandler != NULL; }
+       ///< Returns true if MTD is currently active.
+public:
+  cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData = false, cCamSlot *MasterSlot = NULL);
        ///< Creates a new CAM slot for the given CiAdapter.
        ///< The CiAdapter will take care of deleting the CAM slot,
        ///< so the caller must not delete it!
        ///< If WantsTsData is true, the device this CAM slot is assigned to will
        ///< call the Decrypt() function of this CAM slot, presenting it the complete
        ///< TS data stream of the encrypted programme, including the CA pids.
+       ///< If this CAM slot is basically the same as an other one, MasterSlot can
+       ///< be given to indicate this. This can be used for instance for CAM slots
+       ///< that can do MTD ("Multi Transponder Decryption"), where the first cCamSlot
+       ///< is created without giving a MasterSlot, and all others are given the first
+       ///< one as their MasterSlot. This can speed up the search for a suitable CAM
+       ///< when tuning to an encrypted channel, and it also makes the Setup/CAM menu
+       ///< clearer because only the master CAM slots will be shown there.
   virtual ~cCamSlot();
-  bool Assign(cDevice *Device, bool Query = false);
+  bool IsMasterSlot(void) { return !masterSlot; }
+       ///< Returns true if this CAM slot itself is a master slot (which means that
+       ///< it doesn't have pointer to another CAM slot that's its master).
+  cCamSlot *MasterSlot(void) { return masterSlot ? masterSlot : this; }
+       ///< Returns this CAM slot's master slot, or a pointer to itself if it is a
+       ///< master slot.
+  cCamSlot *MtdSpawn(void);
+       ///< If this CAM slot can do MTD ("Multi Transponder Decryption"),
+       ///< a call to this function returns a cMtdCamSlot with this CAM slot
+       ///< as its master. Otherwise a pointer to this object is returned, which
+       ///< means that MTD is not supported.
+  void TriggerResendPmt(void) { resendPmt = true; }
+       ///< Tells this CAM slot to resend the list of CA_PMTs to the CAM.
+  virtual bool Assign(cDevice *Device, bool Query = false);
        ///< Assigns this CAM slot to the given Device, if this is possible.
        ///< If Query is 'true', the CI adapter of this slot only checks whether
        ///< it can be assigned to the Device, but doesn't actually assign itself to it.
@@ -170,8 +232,16 @@ public:
        ///< device it was previously assigned to. The value of Query
        ///< is ignored in that case, and this function always returns
        ///< 'true'.
+       ///< If a derived class reimplements this function, it can return 'false'
+       ///< if this CAM can't be assigned to the given Device. If the CAM can be
+       ///< assigned to the Device, or if Device is NULL, it must call the base
+       ///< class function.
   cDevice *Device(void) { return assignedDevice; }
        ///< Returns the device this CAM slot is currently assigned to.
+  bool Devices(cVector<int> &CardIndexes);
+       ///< Adds the card indexes of any devices that currently use this CAM to
+       ///< the given CardIndexes. This can be more than one in case of MTD.
+       ///< Returns true if the array is not empty.
   bool WantsTsData(void) const { return caPidReceiver != NULL; }
        ///< Returns true if this CAM slot wants to receive the TS data through
        ///< its Decrypt() function.
@@ -181,6 +251,9 @@ public:
   int SlotNumber(void) { return slotNumber; }
        ///< Returns the number of this CAM slot within the whole system.
        ///< The first slot has the number 1.
+  int MasterSlotNumber(void) { return masterSlot ? masterSlot->SlotNumber() : slotNumber; }
+       ///< Returns the number of this CAM's master slot within the whole system.
+       ///< The first slot has the number 1.
   virtual bool Reset(void);
        ///< Resets the CAM in this slot.
        ///< Returns true if the operation was successful.
@@ -241,7 +314,7 @@ public:
        ///< If the source or transponder of the channel are different than
        ///< what was given in a previous call to AddChannel(), any previously
        ///< added PIDs will be cleared.
-  virtual bool CanDecrypt(const cChannel *Channel);
+  virtual bool CanDecrypt(const cChannel *Channel, cMtdMapper *MtdMapper = NULL);
        ///< Returns true if there is a CAM in this slot that is able to decrypt
        ///< the given Channel (or at least claims to be able to do so).
        ///< Since the QUERY/REPLY mechanism for CAMs is pretty unreliable (some
@@ -252,11 +325,18 @@ public:
        ///< to the initial QUERY will perform this check at all. CAMs that never
        ///< replied to the initial QUERY are assumed not to be able to handle
        ///< more than one channel at a time.
+       ///< If MtdMapper is given, all SIDs and PIDs will be mapped accordingly.
   virtual void StartDecrypting(void);
-       ///< Triggers sending all currently active CA_PMT entries to the CAM,
-       ///< so that it will start decrypting.
+       ///< Sends all CA_PMT entries to the CAM that have been modified since the
+       ///< last call to this function. This includes CA_PMTs that have been
+       ///< added or activated, as well as ones that have been deactivated.
+       ///< StartDecrypting() will be called whenever a PID is activated or
+       ///< deactivated.
   virtual void StopDecrypting(void);
        ///< Clears the list of CA_PMT entries and tells the CAM to stop decrypting.
+       ///< Note that this function is only called when there are no more PIDs for
+       ///< the CAM to decrypt. There is no symmetry between StartDecrypting() and
+       ///< StopDecrypting().
   virtual bool IsDecrypting(void);
        ///< Returns true if the CAM in this slot is currently used for decrypting.
   virtual uchar *Decrypt(uchar *Data, int &Count);
@@ -287,7 +367,8 @@ public:
        ///< the CAM's control). If no decrypted TS packet is currently available, NULL
        ///< shall be returned. If no data from Data can currently be processed, Count
        ///< shall be set to 0 and the same Data pointer will be offered in the next
-       ///< call to Decrypt().
+       ///< call to Decrypt(). See mtd.h for further requirements if this CAM can
+       ///< do MTD ("Multi Transponder Decryption").
        ///< A derived class that implements this function will also need
        ///< to set the WantsTsData parameter in the call to the base class
        ///< constructor to true in order to receive the TS data.
@@ -295,6 +376,9 @@ public:
 
 class cCamSlots : public cList<cCamSlot> {
 public:
+  int NumReadyMasterSlots(void);
+       ///< Returns the number of master CAM slots in the system that are ready
+       ///< to decrypt.
   bool WaitForAllCamSlotsReady(int Timeout = 0);
        ///< Waits until all CAM slots have become ready, or the given Timeout
        ///< (seconds) has expired. While waiting, the Ready() function of each
@@ -310,6 +394,7 @@ class cChannelCamRelation;
 class cChannelCamRelations : public cList<cChannelCamRelation> {
 private:
   cMutex mutex;
+  cString fileName;
   cChannelCamRelation *GetEntry(tChannelID ChannelID);
   cChannelCamRelation *AddEntry(tChannelID ChannelID);
   time_t lastCleanup;
@@ -323,6 +408,8 @@ public:
   void SetDecrypt(tChannelID ChannelID, int CamSlotNumber);
   void ClrChecked(tChannelID ChannelID, int CamSlotNumber);
   void ClrDecrypt(tChannelID ChannelID, int CamSlotNumber);
+  void Load(const char *FileName);
+  void Save(void);
   };
 
 extern cChannelCamRelations ChannelCamRelations;
diff --git a/config.c b/config.c
index e5f5463..79eec8a 100644
--- a/config.c
+++ b/config.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.c 4.4 2015/09/13 11:09:44 kls Exp $
+ * $Id: config.c 4.5 2017/02/14 11:02:48 kls Exp $
  */
 
 #include "config.h"
@@ -640,7 +640,7 @@ bool cSetup::Parse(const char *Name, const char *Value)
   else if (!strcasecmp(Name, "EPGLinger"))           EPGLinger          = atoi(Value);
   else if (!strcasecmp(Name, "SVDRPTimeout"))        SVDRPTimeout       = atoi(Value);
   else if (!strcasecmp(Name, "SVDRPPeering"))        SVDRPPeering       = atoi(Value);
-  else if (!strcasecmp(Name, "SVDRPHostName"))       strn0cpy(SVDRPHostName, Value, sizeof(SVDRPHostName));
+  else if (!strcasecmp(Name, "SVDRPHostName"))     { if (!*SVDRPHostName) strn0cpy(SVDRPHostName, Value, sizeof(SVDRPHostName)); }
   else if (!strcasecmp(Name, "SVDRPdefaultHost"))    strn0cpy(SVDRPDefaultHost, Value, sizeof(SVDRPDefaultHost));
   else if (!strcasecmp(Name, "ZapTimeout"))          ZapTimeout         = atoi(Value);
   else if (!strcasecmp(Name, "ChannelEntryTimeout")) ChannelEntryTimeout= atoi(Value);
diff --git a/config.h b/config.h
index 16630b3..4bfdba5 100644
--- a/config.h
+++ b/config.h
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.h 4.6 2015/09/16 11:11:42 kls Exp $
+ * $Id: config.h 4.7 2016/12/27 11:45:25 kls Exp $
  */
 
 #ifndef __CONFIG_H
@@ -22,13 +22,13 @@
 
 // VDR's own version number:
 
-#define VDRVERSION  "2.3.2"
-#define VDRVERSNUM   20302  // Version * 10000 + Major * 100 + Minor
+#define VDRVERSION  "2.3.3"
+#define VDRVERSNUM   20303  // Version * 10000 + Major * 100 + Minor
 
 // The plugin API's version number:
 
-#define APIVERSION  "2.3.2"
-#define APIVERSNUM   20302  // Version * 10000 + Major * 100 + Minor
+#define APIVERSION  "2.3.3"
+#define APIVERSNUM   20303  // Version * 10000 + Major * 100 + Minor
 
 // When loading plugins, VDR searches them by their APIVERSION, which
 // may be smaller than VDRVERSION in case there have been no changes to
diff --git a/device.c b/device.c
index 3c97b8c..247e8c5 100644
--- a/device.c
+++ b/device.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.c 4.3 2016/12/23 14:43:44 kls Exp $
+ * $Id: device.c 4.11 2017/03/27 14:02:54 kls Exp $
  */
 
 #include "device.h"
@@ -90,6 +90,7 @@ cDevice::cDevice(void)
 
   camSlot = NULL;
   startScrambleDetection = 0;
+  scramblingTimeout = 0;
 
   occupiedTimeout = 0;
 
@@ -251,7 +252,7 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
          SlotPriority[CamSlot->Index()] = MAXPRIORITY + 1; // assumes it can't be used
          if (CamSlot->ModuleStatus() == msReady) {
             if (CamSlot->ProvidesCa(Channel->Caids())) {
-               if (!ChannelCamRelations.CamChecked(Channel->GetChannelID(), CamSlot->SlotNumber())) {
+               if (!ChannelCamRelations.CamChecked(Channel->GetChannelID(), CamSlot->MasterSlotNumber())) {
                   SlotPriority[CamSlot->Index()] = CamSlot->Priority();
                   NumUsableSlots++;
                   }
@@ -280,8 +281,13 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
              continue; // CAM slot can't be used with this device
           bool ndr;
           if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basically able to do the job
-             if (NumUsableSlots && !HasInternalCam && device[i]->CamSlot() && device[i]->CamSlot() != CamSlots.Get(j))
-                ndr = true; // using a different CAM slot requires detaching receivers
+             if (NumUsableSlots && !HasInternalCam) {
+                if (cCamSlot *csi = device[i]->CamSlot()) {
+                   cCamSlot *csj = CamSlots.Get(j);
+                   if ((csj->MtdActive() ? csi->MasterSlot() : csi) != csj)
+                      ndr = true; // using a different CAM slot requires detaching receivers
+                   }
+                }
              // Put together an integer number that reflects the "impact" using
              // this device would have on the overall system. Each condition is represented
              // by one bit in the number (or several bits, if the condition is actually
@@ -299,7 +305,7 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
              imp <<= 1; imp |= ndr;                                                                                  // avoid devices if we need to detach existing receivers
              imp <<= 1; imp |= (NumUsableSlots || InternalCamNeeded) ? 0 : device[i]->HasCi();                       // avoid cards with Common Interface for FTA channels
              imp <<= 1; imp |= device[i]->AvoidRecording();                                                          // avoid SD full featured cards
-             imp <<= 1; imp |= (NumUsableSlots && !HasInternalCam) ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel
+             imp <<= 1; imp |= (NumUsableSlots && !HasInternalCam) ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), CamSlots.Get(j)->MasterSlotNumber()) : 0; // prefer CAMs that are known to decrypt this channel
              imp <<= 1; imp |= device[i]->IsPrimaryDevice();                                                         // avoid the primary device
              if (imp < Impact) {
                 // This device has less impact than any previous one, so we take it.
@@ -314,16 +320,89 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
       if (!NumUsableSlots)
          break; // no CAM necessary, so just one loop over the devices
       }
-  if (d && !Query) {
-     if (NeedsDetachReceivers)
+  if (d) {
+     if (!Query && NeedsDetachReceivers)
         d->DetachAllReceivers();
      if (s) {
-        if (s->Device() != d) {
-           if (s->Device())
-              s->Device()->DetachAllReceivers();
-           if (d->CamSlot())
-              d->CamSlot()->Assign(NULL);
-           s->Assign(d);
+        // Some of the following statements could probably be combined, but let's keep them
+        // explicit so we can clearly see every single aspect of the decisions made here.
+        if (d->CamSlot()) {
+           if (s->MtdActive()) {
+              if (s == d->CamSlot()->MasterSlot()) {
+                 // device d already has a proper CAM slot, so nothing to do here
+                 }
+              else {
+                 // device d has a CAM slot, but it's not the right one
+                 if (!Query) {
+                    d->CamSlot()->Assign(NULL);
+                    s = s->MtdSpawn();
+                    s->Assign(d);
+                    }
+                 }
+              }
+           else {
+              if (s->Device()) {
+                 if (s->Device() != d) {
+                    // CAM slot s is currently assigned to a different device than d
+                    if (Priority > s->Priority()) {
+                       if (!Query) {
+                          d->CamSlot()->Assign(NULL);
+                          s->Assign(d);
+                          }
+                       }
+                    else {
+                       d = NULL;
+                       s = NULL;
+                       }
+                    }
+                 else {
+                    // device d already has a proper CAM slot, so nothing to do here
+                    }
+                 }
+              else {
+                 if (s != d->CamSlot()) {
+                    // device d has a CAM slot, but it's not the right one
+                    if (!Query) {
+                       d->CamSlot()->Assign(NULL);
+                       s->Assign(d);
+                       }
+                    }
+                 else {
+                    // device d already has a proper CAM slot, so nothing to do here
+                    }
+                 }
+              }
+           }
+        else {
+           // device d has no CAM slot, ...
+           if (s->MtdActive()) {
+              // ... so we assign s with MTD support
+              if (!Query) {
+                 s = s->MtdSpawn();
+                 s->Assign(d);
+                 }
+              }
+           else {
+              // CAM slot s has no MTD support ...
+              if (s->Device()) {
+                 // ... but it is assigned to a different device, so we reassign it to d
+                 if (Priority > s->Priority()) {
+                    if (!Query) {
+                       s->Device()->DetachAllReceivers();
+                       s->Assign(d);
+                       }
+                    }
+                 else {
+                    d = NULL;
+                    s = NULL;
+                    }
+                 }
+              else {
+                 // ... and is not assigned to any device, so we just assign it to d
+                 if (!Query)
+                    s->Assign(d);
+                 }
+              }
            }
         }
      else if (d->CamSlot() && !d->CamSlot()->IsDecrypting())
@@ -450,6 +529,7 @@ void cDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect)
 
 bool cDevice::HasPid(int Pid) const
 {
+  cMutexLock MutexLock(&mutexPids);
   for (int i = 0; i < MAXPIDHANDLES; i++) {
       if (pidHandles[i].pid == Pid)
          return true;
@@ -459,6 +539,7 @@ bool cDevice::HasPid(int Pid) const
 
 bool cDevice::AddPid(int Pid, ePidType PidType, int StreamType)
 {
+  cMutexLock MutexLock(&mutexPids);
   if (Pid || PidType == ptPcr) {
      int n = -1;
      int a = -1;
@@ -523,6 +604,7 @@ bool cDevice::AddPid(int Pid, ePidType PidType, int StreamType)
 
 void cDevice::DelPid(int Pid, ePidType PidType)
 {
+  cMutexLock MutexLock(&mutexPids);
   if (Pid || PidType == ptPcr) {
      int n = -1;
      if (PidType == ptPcr)
@@ -558,6 +640,7 @@ bool cDevice::SetPid(cPidHandle *Handle, int Type, bool On)
 
 void cDevice::DelLivePids(void)
 {
+  cMutexLock MutexLock(&mutexPids);
   for (int i = ptAudio; i < ptOther; i++) {
       if (pidHandles[i].pid)
          DelPid(pidHandles[i].pid, ePidType(i));
@@ -1487,13 +1570,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
      }
   else {
      while (Length >= TS_SIZE) {
-           if (Data[0] != TS_SYNC_BYTE) {
-              int Skipped = 1;
-              while (Skipped < Length && (Data[Skipped] != TS_SYNC_BYTE || Length - Skipped > TS_SIZE && Data[Skipped + TS_SIZE] != TS_SYNC_BYTE))
-                    Skipped++;
-              esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
+           if (int Skipped = TS_SYNC(Data, Length))
               return Played + Skipped;
-              }
            int Pid = TsPid(Data);
            if (TsHasPayload(Data)) { // silently ignore TS packets w/o payload
               int PayloadOffset = TsPayloadOffset(Data);
@@ -1574,6 +1652,7 @@ bool cDevice::Receiving(bool Dummy) const
 
 void cDevice::Action(void)
 {
+  time_t LastScrambledPacket = 0;
   if (Running() && OpenDvr()) {
      while (Running()) {
            // Read data from the DVR device:
@@ -1588,17 +1667,18 @@ void cDevice::Action(void)
                  cCamSlot *cs = NULL;
                  if (startScrambleDetection) {
                     cs = CamSlot();
-                    CamSlotNumber = cs ? cs->SlotNumber() : 0;
+                    CamSlotNumber = cs ? cs->MasterSlotNumber() : 0;
                     if (CamSlotNumber) {
-                       int t = time(NULL) - startScrambleDetection;
+                       if (LastScrambledPacket < startScrambleDetection)
+                          LastScrambledPacket = startScrambleDetection;
+                       time_t Now = time(NULL);
                        if (TsIsScrambled(b)) {
-                          if (t > TS_SCRAMBLING_TIMEOUT)
+                          LastScrambledPacket = Now;
+                          if (Now - startScrambleDetection > scramblingTimeout)
                              DetachReceivers = true;
                           }
-                       else if (t > TS_SCRAMBLING_TIME_OK) {
+                       if (Now - LastScrambledPacket > TS_SCRAMBLING_TIME_OK)
                           DescramblingOk = true;
-                          startScrambleDetection = 0;
-                          }
                        }
                     }
                  // Distribute the packet to all attached receivers:
@@ -1606,14 +1686,17 @@ void cDevice::Action(void)
                  for (int i = 0; i < MAXRECEIVERS; i++) {
                      if (receiver[i] && receiver[i]->WantsPid(Pid)) {
                         if (DetachReceivers && cs && (!cs->IsActivating() || receiver[i]->Priority() >= LIVEPRIORITY)) {
-                           dsyslog("detaching receiver - won't decrypt channel %s with CAM %d", *receiver[i]->ChannelID().ToString(), CamSlotNumber);
+                           dsyslog("CAM %d: won't decrypt channel %s, detaching receiver", CamSlotNumber, *receiver[i]->ChannelID().ToString());
                            ChannelCamRelations.SetChecked(receiver[i]->ChannelID(), CamSlotNumber);
                            Detach(receiver[i]);
                            }
                         else
                            receiver[i]->Receive(b, TS_SIZE);
-                        if (DescramblingOk)
+                        if (DescramblingOk && receiver[i]->ChannelID().Valid()) {
+                           dsyslog("CAM %d: decrypts channel %s", CamSlotNumber, *receiver[i]->ChannelID().ToString());
                            ChannelCamRelations.SetDecrypt(receiver[i]->ChannelID(), CamSlotNumber);
+                           startScrambleDetection = 0;
+                           }
                         }
                      }
                  Unlock();
@@ -1672,7 +1755,14 @@ bool cDevice::AttachReceiver(cReceiver *Receiver)
          Unlock();
          if (camSlot && Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver
             camSlot->StartDecrypting();
-            startScrambleDetection = time(NULL);
+            if (CamSlots.NumReadyMasterSlots() > 1) { // don't try different CAMs if there is only one
+               startScrambleDetection = time(NULL);
+               scramblingTimeout = TS_SCRAMBLING_TIMEOUT;
+               bool KnownToDecrypt = ChannelCamRelations.CamDecrypt(Receiver->ChannelID(), camSlot->MasterSlotNumber());
+               if (KnownToDecrypt)
+                  scramblingTimeout *= 10; // give it time to receive ECM/EMM
+               dsyslog("CAM %d: %sknown to decrypt channel %s (scramblingTimeout = %ds)", camSlot->SlotNumber(), KnownToDecrypt ? "" : "not ", *Receiver->ChannelID().ToString(), scramblingTimeout);
+               }
             }
          Start();
          return true;
diff --git a/device.h b/device.h
index 9475d23..581c187 100644
--- a/device.h
+++ b/device.h
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.h 4.2 2016/12/06 14:12:39 kls Exp $
+ * $Id: device.h 4.4 2017/02/21 13:23:24 kls Exp $
  */
 
 #ifndef __DEVICE_H
@@ -109,6 +109,7 @@ public:
 class cDevice : public cThread {
   friend class cLiveSubtitle;
   friend class cDeviceHook;
+  friend class cReceiver;
 private:
   static int numDevices;
   static int useDevice;
@@ -355,6 +356,7 @@ public:
 // PID handle facilities
 
 private:
+  mutable cMutex mutexPids;
   virtual void Action(void);
 protected:
   enum ePidType { ptAudio, ptVideo, ptPcr, ptTeletext, ptDolby, ptOther };
@@ -425,6 +427,7 @@ public:
 
 private:
   time_t startScrambleDetection;
+  int scramblingTimeout;
   cCamSlot *camSlot;
 public:
   virtual bool HasCi(void);
diff --git a/diseqc.c b/diseqc.c
index b2f8e35..c79e6bb 100644
--- a/diseqc.c
+++ b/diseqc.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: diseqc.c 4.0 2015/01/26 12:02:14 kls Exp $
+ * $Id: diseqc.c 4.1 2017/01/09 15:10:40 kls Exp $
  */
 
 #include "diseqc.h"
@@ -253,10 +253,10 @@ bool cDiseqc::Parse(const char *s)
   return result;
 }
 
-uint cDiseqc::SetScrFrequency(uint SatFrequency, const cScr *Scr, uint8_t *Codes) const
+int cDiseqc::SetScrFrequency(int SatFrequency, const cScr *Scr, uint8_t *Codes) const
 {
   if ((Codes[0] & 0xF0) == 0x70 ) { // EN50607 aka JESS
-     uint t = SatFrequency == 0 ? 0 : (SatFrequency - 100);
+     int t = SatFrequency == 0 ? 0 : (SatFrequency - 100);
      if (t < 2048 && Scr->Channel() >= 0 && Scr->Channel() < 32) {
         Codes[1] = t >> 8 | Scr->Channel() << 3;
         Codes[2] = t;
@@ -266,7 +266,7 @@ uint cDiseqc::SetScrFrequency(uint SatFrequency, const cScr *Scr, uint8_t *Codes
         }
      }
   else { // EN50494 aka Unicable
-     uint t = SatFrequency == 0 ? 0 : (SatFrequency + Scr->UserBand() + 2) / 4 - 350; // '+ 2' together with '/ 4' results in rounding!
+     int t = SatFrequency == 0 ? 0 : (SatFrequency + Scr->UserBand() + 2) / 4 - 350; // '+ 2' together with '/ 4' results in rounding!
      if (t < 1024 && Scr->Channel() >= 0 && Scr->Channel() < 8) {
         Codes[3] = t >> 8 | (t == 0 ? 0 : scrBank << 2) | Scr->Channel() << 5;
         Codes[4] = t;
@@ -399,7 +399,7 @@ const char *cDiseqc::GetCodes(const char *s, uchar *Codes, uint8_t *MaxCodes) co
   return NULL;
 }
 
-cDiseqc::eDiseqcActions cDiseqc::Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, uint *Frequency) const
+cDiseqc::eDiseqcActions cDiseqc::Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, int *Frequency) const
 {
   if (!*CurrentAction)
      *CurrentAction = commands;
diff --git a/diseqc.h b/diseqc.h
index 2c349d1..9ae9dc7 100644
--- a/diseqc.h
+++ b/diseqc.h
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: diseqc.h 4.0 2013/06/12 11:52:17 kls Exp $
+ * $Id: diseqc.h 4.1 2017/01/09 15:11:19 kls Exp $
  */
 
 #ifndef __DISEQC_H
@@ -86,7 +86,7 @@ private:
   mutable int scrBank;
   char *commands;
   bool parsing;
-  uint SetScrFrequency(uint SatFrequency, const cScr *Scr, uint8_t *Codes) const;
+  int SetScrFrequency(int SatFrequency, const cScr *Scr, uint8_t *Codes) const;
   int SetScrPin(const cScr *Scr, uint8_t *Codes) const;
   const char *Wait(const char *s) const;
   const char *GetPosition(const char *s) const;
@@ -96,7 +96,7 @@ public:
   cDiseqc(void);
   ~cDiseqc();
   bool Parse(const char *s);
-  eDiseqcActions Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, uint *Frequency) const;
+  eDiseqcActions Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, int *Frequency) const;
       ///< Parses the DiSEqC commands and returns the appropriate action code
       ///< with every call. CurrentAction must be the address of a character pointer,
       ///< which is initialized to NULL. This pointer is used internally while parsing
diff --git a/dvbdevice.c b/dvbdevice.c
index b3d54c6..cd928c2 100644
--- a/dvbdevice.c
+++ b/dvbdevice.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.c 4.3 2016/11/07 13:55:58 kls Exp $
+ * $Id: dvbdevice.c 4.4 2017/01/09 15:11:39 kls Exp $
  */
 
 #include "dvbdevice.h"
@@ -329,7 +329,7 @@ private:
   void ClearEventQueue(void) const;
   bool GetFrontendStatus(fe_status_t &Status) const;
   cPositioner *GetPositioner(void);
-  void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency);
+  void ExecuteDiseqc(const cDiseqc *Diseqc, int *Frequency);
   void ResetToneAndVoltage(void);
   bool SetFrontend(void);
   virtual void Action(void);
@@ -696,7 +696,7 @@ cPositioner *cDvbTuner::GetPositioner(void)
   return positioner;
 }
 
-void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency)
+void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, int *Frequency)
 {
   if (!lnbPowerTurnedOn) {
      CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power
@@ -806,7 +806,7 @@ bool cDvbTuner::SetFrontend(void)
 
   SETCMD(DTV_DELIVERY_SYSTEM, frontendType);
   if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) {
-     unsigned int frequency = channel.Frequency();
+     int frequency = channel.Frequency();
      if (Setup.DiSEqC) {
         if (const cDiseqc *diseqc = Diseqcs.Get(device->CardIndex() + 1, channel.Source(), frequency, dtp.Polarization(), &scr)) {
            frequency -= diseqc->Lof();
@@ -829,7 +829,7 @@ bool cDvbTuner::SetFrontend(void)
         }
      else {
         int tone = SEC_TONE_OFF;
-        if (frequency < (unsigned int)Setup.LnbSLOF) {
+        if (frequency < Setup.LnbSLOF) {
            frequency -= Setup.LnbFrequLo;
            tone = SEC_TONE_OFF;
            }
diff --git a/menu.c b/menu.c
index 30c95f8..685e94f 100644
--- a/menu.c
+++ b/menu.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.c 4.19 2016/12/22 11:00:13 kls Exp $
+ * $Id: menu.c 4.22 2017/03/18 14:27:50 kls Exp $
  */
 
 #include "menu.h"
@@ -3770,8 +3770,18 @@ bool cMenuSetupCAMItem::Changed(void)
   else if (camSlot->IsActivating())
      // TRANSLATORS: note the leading blank!
      Activating = tr(" (activating)");
-  if (cDevice *Device = camSlot->Device())
-     AssignedDevice = cString::sprintf(" %s %d", tr("@ device"), Device->CardIndex() + 1);
+  cVector<int> CardIndexes;
+  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
+      if (CamSlot == camSlot || CamSlot->MasterSlot() == camSlot)
+         CamSlot->Devices(CardIndexes);
+      }
+  if (CardIndexes.Size() > 0) {
+     AssignedDevice = cString::sprintf(" %s", tr("@ device"));
+     CardIndexes.Sort(CompareInts);
+     for (int i = 0; i < CardIndexes.Size(); i++)
+         AssignedDevice = cString::sprintf("%s %d", *AssignedDevice, CardIndexes[i] + 1);
+     }
+
   cString buffer = cString::sprintf(" %d %s%s%s", camSlot->SlotNumber(), CamName, *AssignedDevice, Activating);
   if (strcmp(buffer, Text()) != 0) {
      SetText(buffer);
@@ -3799,8 +3809,10 @@ cMenuSetupCAM::cMenuSetupCAM(void)
   SetSection(tr("CAM"));
   SetCols(15);
   SetHasHotkeys();
-  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot))
-      Add(new cMenuSetupCAMItem(CamSlot));
+  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
+      if (CamSlot->IsMasterSlot()) // we only list master CAM slots
+         Add(new cMenuSetupCAMItem(CamSlot));
+      }
   SetHelpKeys();
 }
 
@@ -3865,14 +3877,13 @@ eOSState cMenuSetupCAM::Activate(void)
                   if (cDevice *Device = cDevice::GetDevice(i)) {
                      if (Device->ProvidesChannel(Channel)) {
                         if (Device->Priority() < LIVEPRIORITY) { // don't interrupt recordings
-                           if (CamSlot->CanActivate()) {
-                              if (CamSlot->Assign(Device, true)) { // query
-                                 cControl::Shutdown(); // must end transfer mode before assigning CAM, otherwise it might be unassigned again
-                                 if (CamSlot->Assign(Device)) {
-                                    if (Device->SwitchChannel(Channel, true)) {
-                                       CamSlot->StartActivation();
-                                       return osContinue;
-                                       }
+                           if (CamSlot->Assign(Device, true)) { // query
+                              cControl::Shutdown(); // must end transfer mode before assigning CAM, otherwise it might be unassigned again
+                              CamSlot = CamSlot->MtdSpawn();
+                              if (CamSlot->Assign(Device)) {
+                                 if (Device->SwitchChannel(Channel, true)) {
+                                    CamSlot->StartActivation();
+                                    return osContinue;
                                     }
                                  }
                               }
@@ -5584,7 +5595,7 @@ void cReplayControl::ShowMode(void)
 bool cReplayControl::ShowProgress(bool Initial)
 {
   int Current, Total;
-  if (Initial || time(NULL) - lastProgressUpdate >= 1) {
+  if (Initial || lastSpeed != -1 || time(NULL) - lastProgressUpdate >= 1) {
      if (GetFrameNumber(Current, Total) && Total > 0) {
         if (!visible) {
            displayReplay = Skins.Current()->DisplayReplay(modeOnly);
diff --git a/mtd.c b/mtd.c
new file mode 100644
index 0000000..bf792dd
--- /dev/null
+++ b/mtd.c
@@ -0,0 +1,341 @@
+/*
+ * mtd.c: Multi Transponder Decryption
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: mtd.c 1.9 2017/03/27 14:26:04 kls Exp $
+ */
+
+#include "mtd.h"
+#include "receiver.h"
+
+//#define DEBUG_MTD
+#ifdef DEBUG_MTD
+#define DBGMTD(a...) dsyslog(a)
+#else
+#define DBGMTD(a...)
+#endif
+
+//#define KEEPPIDS // for testing and debugging - USE ONLY IF YOU KNOW WHAT YOU ARE DOING!
+
+#define MAX_REAL_PIDS  MAXPID // real PIDs are 13 bit (0x0000 - 0x1FFF)
+#ifdef KEEPPIDS
+#define MAX_UNIQ_PIDS  MAXPID
+#define UNIQ_PID_MASK  0x1FFF
+#else
+#define MAX_UNIQ_PIDS  256    // uniq PIDs are 8 bit (0x00 - 0xFF)
+#define UNIQ_PID_MASK  0x00FF
+#define UNIQ_PID_SHIFT 8
+#endif // KEEPPIDS
+
+// --- cMtdHandler -----------------------------------------------------------
+
+cMtdHandler::cMtdHandler(void)
+{
+}
+
+cMtdHandler::~cMtdHandler()
+{
+  for (int i = 0; i < camSlots.Size(); i++) {
+      dsyslog("CAM %d/%d: deleting MTD CAM slot", camSlots[i]->MasterSlot()->SlotNumber(), i + 1);
+      delete camSlots[i];
+      }
+}
+
+cMtdCamSlot *cMtdHandler::GetMtdCamSlot(cCamSlot *MasterSlot)
+{
+  for (int i = 0; i < camSlots.Size(); i++) {
+      if (!camSlots[i]->Device()) {
+         dsyslog("CAM %d/%d: reusing MTD CAM slot", MasterSlot->SlotNumber(), i + 1);
+         return camSlots[i];
+         }
+      }
+  dsyslog("CAM %d/%d: creating new MTD CAM slot", MasterSlot->SlotNumber(), camSlots.Size() + 1);
+  cMtdCamSlot *s = new cMtdCamSlot(MasterSlot, camSlots.Size());
+  camSlots.Append(s);
+  return s;
+}
+
+int cMtdHandler::Put(const uchar *Data, int Count)
+{
+  int Used = 0;
+  while (Count >= TS_SIZE) {
+        if (int Skipped = TS_SYNC(Data, Count))
+           return Used + Skipped;
+        int Pid = TsPid(Data);
+        if (Pid != CATPID) { // the original CAT with mapped PIDs must be skipped here!
+#ifdef KEEPPIDS
+           int Index = 0;
+#else
+           int Index = (Pid >> UNIQ_PID_SHIFT) - 1;
+#endif // KEEPPIDS
+           if (Index >= 0 && Index < camSlots.Size()) {
+              int w = camSlots[Index]->PutData(Data, TS_SIZE);
+              if (w == 0)
+                 break;
+              else if (w != TS_SIZE)
+                 esyslog("ERROR: incomplete MTD packet written (%d) in PID %d (%04X)", Index + 1, Pid, Pid);
+              }
+           else if (Index >= 0) // we silently ignore Index -1 (i.e. MTD number 0), since there are several hundred empty TS packets when switching to an encrypted channel for the first time since startup
+              esyslog("ERROR: invalid MTD number (%d) in PID %d (%04X)", Index + 1, Pid, Pid);
+           }
+        Data += TS_SIZE;
+        Count -= TS_SIZE;
+        Used += TS_SIZE;
+        }
+  return Used;
+}
+
+int cMtdHandler::Priority(void)
+{
+  int p = IDLEPRIORITY;
+  for (int i = 0; i < camSlots.Size(); i++)
+      p = max(p, camSlots[i]->Priority());
+  return p;
+}
+
+bool cMtdHandler::IsDecrypting(void)
+{
+  for (int i = 0; i < camSlots.Size(); i++) {
+      if (camSlots[i]->IsDecrypting())
+         return true;
+      }
+  return false;
+}
+
+void cMtdHandler::StartDecrypting(void)
+{
+  for (int i = 0; i < camSlots.Size(); i++) {
+      if (camSlots[i]->Device()) {
+         camSlots[i]->TriggerResendPmt();
+         camSlots[i]->StartDecrypting();
+         }
+      }
+}
+
+void cMtdHandler::CancelActivation(void)
+{
+  for (int i = 0; i < camSlots.Size(); i++)
+      camSlots[i]->CancelActivation();
+}
+
+bool cMtdHandler::IsActivating(void)
+{
+  for (int i = 0; i < camSlots.Size(); i++) {
+      if (camSlots[i]->IsActivating())
+         return true;
+      }
+  return false;
+}
+
+bool cMtdHandler::Devices(cVector<int> &CardIndexes)
+{
+  for (int i = 0; i < camSlots.Size(); i++)
+      camSlots[i]->Devices(CardIndexes);
+  return CardIndexes.Size() > 0;
+}
+
+// --- cMtdMapper ------------------------------------------------------------
+
+#define MTD_INVALID_PID 0xFFFF
+
+class cMtdMapper {
+private:
+  int number;
+  int masterCamSlotNumber;
+  uint16_t uniqPids[MAX_REAL_PIDS]; // maps a real PID to a unique PID
+  uint16_t realPids[MAX_UNIQ_PIDS]; // maps a unique PID to a real PID
+  cVector<uint16_t> uniqSids;
+  uint16_t MakeUniqPid(uint16_t RealPid);
+public:
+  cMtdMapper(int Number, int MasterCamSlotNumber);
+  ~cMtdMapper();
+  uint16_t RealToUniqPid(uint16_t RealPid) { if (uniqPids[RealPid]) return uniqPids[RealPid]; return MakeUniqPid(RealPid); }
+  uint16_t UniqToRealPid(uint16_t UniqPid) { return realPids[UniqPid & UNIQ_PID_MASK]; }
+  uint16_t RealToUniqSid(uint16_t RealSid);
+  void Clear(void);
+  };
+
+cMtdMapper::cMtdMapper(int Number, int MasterCamSlotNumber)
+{
+  number = Number;
+  masterCamSlotNumber = MasterCamSlotNumber;
+  Clear();
+}
+
+cMtdMapper::~cMtdMapper()
+{
+}
+
+uint16_t cMtdMapper::MakeUniqPid(uint16_t RealPid)
+{
+#ifdef KEEPPIDS
+  uniqPids[RealPid] = realPids[RealPid] = RealPid;
+  DBGMTD("CAM %d/%d: mapped PID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealPid, RealPid, uniqPids[RealPid], uniqPids[RealPid]);
+  return uniqPids[RealPid];
+#else
+  for (int i = 0; i < MAX_UNIQ_PIDS; i++) {
+      if (realPids[i] == MTD_INVALID_PID) { // 0x0000 is a valid PID (PAT)!
+         realPids[i] = RealPid;
+         uniqPids[RealPid] = (number << UNIQ_PID_SHIFT) | i;
+         DBGMTD("CAM %d/%d: mapped PID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealPid, RealPid, uniqPids[RealPid], uniqPids[RealPid]);
+         return uniqPids[RealPid];
+         }
+      }
+#endif // KEEPPIDS
+  esyslog("ERROR: MTD %d: mapper ran out of unique PIDs", number);
+  return 0;
+}
+
+uint16_t cMtdMapper::RealToUniqSid(uint16_t RealSid)
+{
+#ifdef KEEPPIDS
+  return RealSid;
+#endif // KEEPPIDS
+  int UniqSid = uniqSids.IndexOf(RealSid);
+  if (UniqSid < 0) {
+     UniqSid = uniqSids.Size();
+     uniqSids.Append(RealSid);
+     DBGMTD("CAM %d/%d: mapped SID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealSid, RealSid, UniqSid | (number << UNIQ_PID_SHIFT), UniqSid | (number << UNIQ_PID_SHIFT));
+     }
+  UniqSid |= number << UNIQ_PID_SHIFT;
+  return UniqSid;
+}
+
+void cMtdMapper::Clear(void)
+{
+  DBGMTD("CAM %d/%d: MTD mapper cleared", masterCamSlotNumber, number);
+  memset(uniqPids, 0, sizeof(uniqPids));
+  memset(realPids, MTD_INVALID_PID, sizeof(realPids));
+  uniqSids.Clear();
+}
+
+void MtdMapSid(uchar *p, cMtdMapper *MtdMapper)
+{
+  Poke13(p, MtdMapper->RealToUniqSid(Peek13(p)));
+}
+
+void MtdMapPid(uchar *p, cMtdMapper *MtdMapper)
+{
+  Poke13(p, MtdMapper->RealToUniqPid(Peek13(p)));
+}
+
+// --- cMtdCamSlot -----------------------------------------------------------
+
+#define MTD_BUFFER_SIZE MEGABYTE(1)
+
+cMtdCamSlot::cMtdCamSlot(cCamSlot *MasterSlot, int Index)
+:cCamSlot(NULL, true, MasterSlot)
+{
+  mtdBuffer = new cRingBufferLinear(MTD_BUFFER_SIZE, TS_SIZE, true, "MTD buffer");
+  mtdMapper = new cMtdMapper(Index + 1, MasterSlot->SlotNumber());
+  delivered = false;
+  ciAdapter = MasterSlot->ciAdapter; // we don't pass the CI adapter in the constructor, to prevent this one from being inserted into CamSlots
+}
+
+cMtdCamSlot::~cMtdCamSlot()
+{
+  Assign(NULL);
+  delete mtdMapper;
+  delete mtdBuffer;
+}
+
+const int *cMtdCamSlot::GetCaSystemIds(void)
+{
+  return MasterSlot()->GetCaSystemIds();
+}
+
+void cMtdCamSlot::SendCaPmt(uint8_t CmdId)
+{
+  cMutexLock MutexLock(&mutex);
+  cCiCaPmtList CaPmtList;
+  BuildCaPmts(CmdId, CaPmtList, mtdMapper);
+  MasterSlot()->SendCaPmts(CaPmtList);
+}
+
+bool cMtdCamSlot::RepliesToQuery(void)
+{
+  return MasterSlot()->RepliesToQuery();
+}
+
+bool cMtdCamSlot::ProvidesCa(const int *CaSystemIds)
+{
+  return MasterSlot()->ProvidesCa(CaSystemIds);
+}
+
+bool cMtdCamSlot::CanDecrypt(const cChannel *Channel, cMtdMapper *MtdMapper)
+{
+  return MasterSlot()->CanDecrypt(Channel, mtdMapper);
+}
+
+void cMtdCamSlot::StartDecrypting(void)
+{
+  MasterSlot()->StartDecrypting();
+  cCamSlot::StartDecrypting();
+}
+
+void cMtdCamSlot::StopDecrypting(void)
+{
+  cCamSlot::StopDecrypting();
+  if (!MasterSlot()->IsDecrypting())
+     MasterSlot()->StopDecrypting();
+  cMutexLock MutexLock(&clearMutex);
+  mtdMapper->Clear();
+  mtdBuffer->Clear();
+  delivered = false;
+}
+
+uchar *cMtdCamSlot::Decrypt(uchar *Data, int &Count)
+{
+  // Send data to CAM:
+  if (Count >= TS_SIZE) {
+     Count = TS_SIZE;
+     int Pid = TsPid(Data);
+     TsSetPid(Data, mtdMapper->RealToUniqPid(Pid));
+     MasterSlot()->Decrypt(Data, Count);
+     if (Count == 0)
+        TsSetPid(Data, Pid); // must restore PID for later retry
+     }
+  else
+     Count = 0;
+  // Drop delivered data from previous call:
+  cMutexLock MutexLock(&clearMutex);
+  if (delivered) {
+     mtdBuffer->Del(TS_SIZE);
+     delivered = false;
+     }
+  // Receive data from buffer:
+  int c = 0;
+  uchar *d = mtdBuffer->Get(c);
+  if (d) {
+     if (int Skipped = TS_SYNC(d, c)) {
+        mtdBuffer->Del(Skipped);
+        return NULL;
+        }
+     if (c >= TS_SIZE) {
+        TsSetPid(d, mtdMapper->UniqToRealPid(TsPid(d)));
+        delivered = true;
+        }
+     else
+        d = NULL;
+     }
+  return d;
+}
+
+int cMtdCamSlot::PutData(const uchar *Data, int Count)
+{
+  int Free = mtdBuffer->Free();
+  Free -= Free % TS_SIZE;
+  if (Free < TS_SIZE)
+     return 0;
+  if (Free < Count)
+     Count = Free;
+  return mtdBuffer->Put(Data, Count);
+}
+
+int cMtdCamSlot::PutCat(const uchar *Data, int Count)
+{
+  MasterSlot()->Decrypt(const_cast<uchar *>(Data), Count);
+  return Count;
+}
diff --git a/mtd.h b/mtd.h
new file mode 100644
index 0000000..b774202
--- /dev/null
+++ b/mtd.h
@@ -0,0 +1,188 @@
+/*
+ * mtd.h: Multi Transponder Decryption
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: mtd.h 1.6 2017/03/27 08:30:00 kls Exp $
+ */
+
+#ifndef __MTD_H
+#define __MTD_H
+
+/*
+Multiple Transponder Decryption (MTD) is the method of sending TS packets
+from channels on different transponders to one single CAM for decryption.
+While decrypting several channels from the same transponder ("Multi Channel
+Decryption") is straightforward, because the PIDs are unique within one
+transponder, channels on different transponders might use the same PIDs
+for different streams.
+
+Here's a summary of how MTD is implemented in VDR:
+
+Identifying the relevant source code
+------------------------------------
+
+The actual code that implements the MTD handling is located in the files
+mtd.h and mtd.c. There are also a few places in ci.[hc], device.c and
+menu.c where things need to be handled differently for MTD. All functions
+and variables that have to do with MTD have the three letters "mtd" (upper-
+and/or lowercase) in their name, so that these code lines can be easily
+identified if necessary.
+
+What a plugin implementing a cCiAdapter/cCamSlot needs to do
+------------------------------------------------------------
+
+If an implementation of cCiAdapter/cCamSlot supports MTD, it needs to
+fulfill the following requirements:
+- The cCiAdapter's Assign() function needs to return true for any given
+  device.
+- The cCamSlot's constructor needs to call MtdEnable().
+- The cCamSlot's Decrypt() function shall accept the given TS packet,
+  but shall *not* return a decrypted packet. Decypted packets shall be
+  delivered through a call to MtdPutData(), one at a time.
+- The cCamSlot's Decrypt() function needs to be thread safe, because
+  it will be called from several cMtdCamSlot objects.
+
+Physical vs. virtual CAMs
+-------------------------
+
+MTD is done by having one physical CAM (accessed through a plugin's
+implementation of cCiAdapter/cCamSlot) and several "virtual" CAMs,
+implemented through cMtdCamSlot objects ("MTD CAMs"). For each device
+that requires the physical CAM, one instance of a cMtdCamSlot is created
+on the fly at runtime, and that MTD CAM is assigned to the device.
+The MTD CAM takes care of mapping the PIDs, and a cMtdHandler in the
+physical CAM object distributes the decrypted TS packets to the proper
+devices.
+
+Mapping the PIDs
+----------------
+
+The main problem with MTD is that the TS packets from different devices
+(and thus different transponders with possibly overlapping PIDs) need to
+be combined into one stream, sent to the physical CAM, and finally be sorted
+apart again and returned to the devices they came from. Both aspects are
+solved in VDR by mapping the "real" PIDs into "unique" PIDs. Real PIDs
+are in the range 0x0000-0x1FFF (13 bit). Unique PIDs use the upper 5 bit
+to indicate the number of the MTD CAM a TS packet came from, and the lower
+8 bit as individual PID values. Mapping is done with a single array lookup
+and is thus very fast. The cMtdHandler class takes care of distributing
+the TS packets to the individual cMtdCamSlot objects, while mapping the
+PIDs (in both directions) is done by the cMtdMapper class.
+
+Mapping the SIDs
+----------------
+
+Besides the PIDs there are also the "service ids" (SIDs, a.k.a. "programme
+numbers" or PNRs) that need to be taken care of. SIDs only appear in the
+CA-PMTs sent to the CAM, so they only need to be mapped from real to unique
+(not the other way) and since the are only mapped when switching channels,
+mapping doesn't need to be very fast. Mapping SIDs is also done by the
+cMtdMapper class.
+
+Handling the CAT
+----------------
+
+Each transponder carries a CAT ("Conditional Access Table") with the fixed PID 1.
+The CAT contains a list of EMM PIDs, which are necessary to convey entitlement
+messages to the smart card. Since the CAM only recognizes the CAT if it has
+its fixed PID of 1, this PID cannot be mapped and has to be sent to the CAM
+as is. However, the cCaPidReceiver also needs to see the CAM in order to
+request from the device the TS packets with the EMM PIDs. Since any receivers
+only get the TS packets after they have been sent through the CAM, we need
+to send the CAT in both ways, with mapped PID but unmapped EMM PIDs for the
+cCaPidReceiver, and with unmapped PID but mapped EMM PIDs for the CAM itself.
+Since the PID 0x0001 can always be distinguished from any mapped PID (which
+always have a non-zero upper byte), the CAT can be easily channeled in both
+ways.
+
+Handling the CA-PMTs
+--------------------
+
+The CA-PMTs that are sent to the CAM contain both SIDs and PIDs, which are
+mapped in cCiCaPmt::MtdMapPids().
+*/
+
+#include "ci.h"
+#include "remux.h"
+#include "ringbuffer.h"
+
+class cMtdHandler {
+private:
+  cVector<cMtdCamSlot *> camSlots;
+public:
+  cMtdHandler(void);
+      ///< Creates a new MTD handler that distributes TS data received through
+      ///< calls to the Put() function to the individual CAM slots that have been
+      ///< created via GetMtdCamSlot(). It also distributes several function
+      ///< calls from the physical master CAM slot to the individual MTD CAM slots.
+  ~cMtdHandler();
+  cMtdCamSlot *GetMtdCamSlot(cCamSlot *MasterSlot);
+      ///< Creates a new MTD CAM slot, or reuses an existing one that is currently
+      ///< unused.
+  int Put(const uchar *Data, int Count);
+      ///< Puts at most Count bytes of Data into the CAM slot which's index is
+      ///< derived from the PID of the TS packets.
+      ///< Data must point to the beginning of a TS packet.
+      ///< Returns the number of bytes actually stored.
+  int Priority(void);
+      ///< Returns the maximum priority of any of the active MTD CAM slots.
+  bool IsDecrypting(void);
+      ///< Returns true if any of the active MTD CAM slots is currently decrypting.
+  void StartDecrypting(void);
+      ///< Tells all active MTD CAM slots to start decrypting.
+  void CancelActivation(void);
+      ///< Tells all active MTD CAM slots to cancel activation.
+  bool IsActivating(void);
+      ///< Returns true if any of the active MTD CAM slots is currently activating.
+  bool Devices(cVector<int> &CardIndexes);
+      ///< Adds the card indexes of the devices of any active MTD CAM slots to
+      ///< the given CardIndexes.
+      ///< Returns true if the array is not empty.
+  };
+
+#define MTD_DONT_CALL(v) dsyslog("PROGRAMMING ERROR (%s,%d): DON'T CALL %s", __FILE__, __LINE__, __FUNCTION__); return v;
+
+class cMtdMapper;
+
+void MtdMapSid(uchar *p, cMtdMapper *MtdMapper);
+void MtdMapPid(uchar *p, cMtdMapper *MtdMapper);
+
+class cMtdCamSlot : public cCamSlot {
+private:
+  cMutex clearMutex;
+  cMtdMapper *mtdMapper;
+  cRingBufferLinear *mtdBuffer;
+  bool delivered;
+protected:
+  virtual const int *GetCaSystemIds(void);
+  virtual void SendCaPmt(uint8_t CmdId);
+public:
+  cMtdCamSlot(cCamSlot *MasterSlot, int Index);
+       ///< Creates a new "Multi Transponder Decryption" CAM slot, connected to the
+       ///< given physical MasterSlot, using the given Index for mapping PIDs.
+  virtual ~cMtdCamSlot();
+  cMtdMapper *MtdMapper(void) { return mtdMapper; }
+  virtual bool RepliesToQuery(void);
+  virtual bool ProvidesCa(const int *CaSystemIds);
+  virtual bool CanDecrypt(const cChannel *Channel, cMtdMapper *MtdMapper = NULL);
+  virtual void StartDecrypting(void);
+  virtual void StopDecrypting(void);
+  virtual uchar *Decrypt(uchar *Data, int &Count);
+  int PutData(const uchar *Data, int Count);
+  int PutCat(const uchar *Data, int Count);
+  // The following functions shall not be called for a cMtdCamSlot:
+  virtual cCamSlot *Spawn(void) { MTD_DONT_CALL(NULL); }
+  virtual bool Reset(void) { MTD_DONT_CALL(false); }
+  virtual eModuleStatus ModuleStatus(void) { MTD_DONT_CALL(msNone); }
+  virtual const char *GetCamName(void) { MTD_DONT_CALL(NULL); }
+  virtual bool Ready(void) { MTD_DONT_CALL(false); }
+  virtual bool HasMMI(void) { MTD_DONT_CALL(false); }
+  virtual bool HasUserIO(void) { MTD_DONT_CALL(false); }
+  virtual bool EnterMenu(void) { MTD_DONT_CALL(false); }
+  virtual cCiMenu *GetMenu(void) { MTD_DONT_CALL(NULL); }
+  virtual cCiEnquiry *GetEnquiry(void) { MTD_DONT_CALL(NULL); }
+  };
+
+#endif //__MTD_H
diff --git a/po/ru_RU.po b/po/ru_RU.po
index eea8b8d..f4b3096 100644
--- a/po/ru_RU.po
+++ b/po/ru_RU.po
@@ -8,552 +8,552 @@ msgid ""
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs at tvdr.de>\n"
-"POT-Creation-Date: 2015-09-11 10:38+0200\n"
-"PO-Revision-Date: 2013-03-10 17:13+0100\n"
-"Last-Translator: Oleg Roitburd <oroitburd at gmail.com>\n"
+"POT-Creation-Date: 2017-01-05 19:50+1000\n"
+"PO-Revision-Date: 2016-12-27 17:13+0100\n"
+"Last-Translator: Pridvorov Andrey <ua0lnj at bk.ru>\n"
 "Language-Team: Russian <vdr at linuxtv.org>\n"
 "Language: ru\n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=ISO-8859-5\n"
+"Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
 msgid "*** Invalid Channel ***"
-msgstr "*** ������������ ����� ***"
+msgstr "*** Неправильный канал ***"
 
 msgid "CAM activated!"
-msgstr ""
+msgstr "МД активен!"
 
 msgid "Channel not available!"
-msgstr "����� ����������!"
+msgstr "Канал недоступен!"
 
 msgid "Can't start Transfer Mode!"
-msgstr "���������� �������� ����� ��������!"
+msgstr "Невозможно включить режим пропуска!"
 
 msgid "off"
-msgstr "����"
+msgstr "выкл"
 
 msgid "on"
-msgstr "���"
+msgstr "вкл"
 
 msgid "auto"
-msgstr "����"
+msgstr "авто"
 
 msgid "none"
-msgstr "������"
+msgstr "ничего"
 
 msgid "Polarization"
-msgstr "�����������"
+msgstr "Поляризация"
 
 msgid "System"
-msgstr "�������"
+msgstr "Система"
 
 msgid "Srate"
-msgstr "����. ��������"
+msgstr "Симв. скорость"
 
 msgid "Inversion"
-msgstr "��������"
+msgstr "Инверсия"
 
 msgid "CoderateH"
-msgstr "CoderateH"
+msgstr "Выс.поток"
 
 msgid "CoderateL"
-msgstr "CoderateL"
+msgstr "Низ.поток"
 
 msgid "Modulation"
-msgstr "���������"
+msgstr "Модуляция"
 
 msgid "Bandwidth"
-msgstr "��������"
+msgstr "Полоса"
 
 msgid "Transmission"
-msgstr "��������"
+msgstr "Передача"
 
 msgid "Guard"
-msgstr "������"
+msgstr "Защита"
 
 msgid "Hierarchy"
-msgstr "��������"
+msgstr "Иерархия"
 
 msgid "Rolloff"
-msgstr "Rolloff"
+msgstr "Крутизна"
 
 msgid "StreamId"
 msgstr "StreamId"
 
 msgid "Pilot"
-msgstr ""
+msgstr "Пилот"
 
 msgid "T2SystemId"
-msgstr ""
+msgstr "T2SystemId"
 
 msgid "SISO/MISO"
-msgstr ""
+msgstr "SISO/MISO"
 
 msgid "Starting EPG scan"
-msgstr "������� EPG-������������"
+msgstr "Запуск EPG-сканирования"
 
 msgid "Content$Movie/Drama"
-msgstr "�����/�����"
+msgstr "Фильм/Драма"
 
 msgid "Content$Detective/Thriller"
-msgstr "��������/�������"
+msgstr "Детектив/Триллер"
 
 msgid "Content$Adventure/Western/War"
-msgstr "�����������/�������/�����"
+msgstr "Приключение/Вестерн/Война"
 
 msgid "Content$Science Fiction/Fantasy/Horror"
-msgstr "������� ����������/��������/����"
+msgstr "Научная фантастика/Фантазия/Ужас"
 
 msgid "Content$Comedy"
-msgstr "�������"
+msgstr "Комедия"
 
 msgid "Content$Soap/Melodrama/Folkloric"
-msgstr "������� �����/���������/��������"
+msgstr "Мыльная опера/Мелодрама/Фольклор"
 
 msgid "Content$Romance"
-msgstr "���������"
+msgstr "Романтика"
 
 msgid "Content$Serious/Classical/Religious/Historical Movie/Drama"
-msgstr "�����������/��������/�����������/������������ �����/�����"
+msgstr "Серьезность/Классика/Религиозное/Исторический фильм/Драма"
 
 msgid "Content$Adult Movie/Drama"
-msgstr "����� ��� ��������/�����"
+msgstr "Фильм для взрослых/Драма"
 
 msgid "Content$News/Current Affairs"
-msgstr "�������/��������� �������"
+msgstr "Новости/Актульные события"
 
 msgid "Content$News/Weather Report"
-msgstr "�������/������"
+msgstr "Новости/Погода"
 
 msgid "Content$News Magazine"
-msgstr "��������� ������"
+msgstr "Новостной журнал"
 
 msgid "Content$Documentary"
-msgstr "������������"
+msgstr "Документальные"
 
 msgid "Content$Discussion/Inverview/Debate"
-msgstr "���������/��������/������"
+msgstr "Дискуссия/Интервью/Дебаты"
 
 msgid "Content$Show/Game Show"
-msgstr "���/������� ���"
+msgstr "Шоу/Игровое шоу"
 
 msgid "Content$Game Show/Quiz/Contest"
-msgstr "������� ���/���������/������������"
+msgstr "Игровое шоу/Викторина/Соревнование"
 
 msgid "Content$Variety Show"
-msgstr "��������"
+msgstr "Варьетте"
 
 msgid "Content$Talk Show"
-msgstr "������"
+msgstr "Токшоу"
 
 msgid "Content$Sports"
-msgstr "�����"
+msgstr "Спорт"
 
 msgid "Content$Special Event"
-msgstr "��������� �������"
+msgstr "Особенное событие"
 
 msgid "Content$Sport Magazine"
-msgstr "����� ������"
+msgstr "Спорт журнал"
 
 msgid "Content$Football/Soccer"
-msgstr "������"
+msgstr "Футбол"
 
 msgid "Content$Tennis/Squash"
-msgstr "������/�����"
+msgstr "Теннис/Сквош"
 
 msgid "Content$Team Sports"
-msgstr "���������� ���� ������"
+msgstr "Коммандные виды спорта"
 
 msgid "Content$Athletics"
-msgstr "��������"
+msgstr "Атлетика"
 
 msgid "Content$Motor Sport"
-msgstr "����������"
+msgstr "Моторспорт"
 
 msgid "Content$Water Sport"
-msgstr "������ ���� ������"
+msgstr "Водный виды спорта"
 
 msgid "Content$Winter Sports"
-msgstr "������ ���� ������"
+msgstr "Зимние виды спорта"
 
 msgid "Content$Equestrian"
-msgstr "������ �����"
+msgstr "Конный спорт"
 
 msgid "Content$Martial Sports"
-msgstr "������ ���������"
+msgstr "Боевые искусства"
 
 msgid "Content$Children's/Youth Programme"
-msgstr "�������/��������� ���������"
+msgstr "Детская/Юнешеская программа"
 
 msgid "Content$Pre-school Children's Programme"
-msgstr "��������� ��� ������������"
+msgstr "Программа для дошкольников"
 
 msgid "Content$Entertainment Programme for 6 to 14"
-msgstr "��������������� ��������� �� 6 �� 14"
+msgstr "Развлекательная программа от 6 до 14"
 
 msgid "Content$Entertainment Programme for 10 to 16"
-msgstr "��������������� ��������� �� 10 �� 16"
+msgstr "Развлекательная программа от 10 до 16"
 
 msgid "Content$Informational/Educational/School Programme"
-msgstr "��������������/���������������/�������� ���������"
+msgstr "Информационная/Образовательная/Школьная программа"
 
 msgid "Content$Cartoons/Puppets"
-msgstr "�������������/�����"
+msgstr "Мултипликация/Куклы"
 
 msgid "Content$Music/Ballet/Dance"
-msgstr "������/�����/�����"
+msgstr "Музыка/Балет/Танец"
 
 msgid "Content$Rock/Pop"
-msgstr "���/���"
+msgstr "Рок/Поп"
 
 msgid "Content$Serious/Classical Music"
-msgstr "������������ ������"
+msgstr "Классическая музыка"
 
 msgid "Content$Folk/Tradional Music"
-msgstr "��������/������������ ������"
+msgstr "Фольклор/Традиционная музыка"
 
 msgid "Content$Jazz"
-msgstr "����"
+msgstr "Джаз"
 
 msgid "Content$Musical/Opera"
-msgstr "������/�����"
+msgstr "Мюзикл/Опера"
 
 msgid "Content$Ballet"
-msgstr "�����"
+msgstr "Балет"
 
 msgid "Content$Arts/Culture"
-msgstr "���������/��������"
+msgstr "Искусство/Культура"
 
 msgid "Content$Performing Arts"
-msgstr "�������������"
+msgstr "Перформэнсарт"
 
 msgid "Content$Fine Arts"
-msgstr "������� ���������"
+msgstr "Изящное искусство"
 
 msgid "Content$Religion"
-msgstr "�������"
+msgstr "Религия"
 
 msgid "Content$Popular Culture/Traditional Arts"
-msgstr "�����������/������������ ��������"
+msgstr "Попкультура/Традиционная культура"
 
 msgid "Content$Literature"
-msgstr "����������"
+msgstr "Литература"
 
 msgid "Content$Film/Cinema"
-msgstr "�����/�����"
+msgstr "Фильм/Кино"
 
 msgid "Content$Experimental Film/Video"
-msgstr "����������������� �����/�����"
+msgstr "Экспериментальный фильм/Видео"
 
 msgid "Content$Broadcasting/Press"
-msgstr "������� �������/������"
+msgstr "Широкое вещание/Печать"
 
 msgid "Content$New Media"
-msgstr "����� ��������"
+msgstr "Новые средства"
 
 msgid "Content$Arts/Culture Magazine"
-msgstr "���������/���������� ������"
+msgstr "Искусство/Культурный журнал"
 
 msgid "Content$Fashion"
-msgstr "����"
+msgstr "Мода"
 
 msgid "Content$Social/Political/Economics"
-msgstr "����������/������������/�������������"
+msgstr "Социальное/Политическое/Экономическое"
 
 msgid "Content$Magazine/Report/Documentary"
-msgstr "������/���������/������������"
+msgstr "Журнал/Экономика/Документальные"
 
 msgid "Content$Economics/Social Advisory"
-msgstr "���������/��������������"
+msgstr "Экономика/Общественность"
 
 msgid "Content$Remarkable People"
-msgstr "���������� ����"
+msgstr "Знаменитые люди"
 
 msgid "Content$Education/Science/Factual"
-msgstr "�����������/�����/�����"
+msgstr "Образование/Наука/Факты"
 
 msgid "Content$Nature/Animals/Environment"
-msgstr "�������/��������/���������"
+msgstr "Природа/Животные/Окружение"
 
 msgid "Content$Technology/Natural Sciences"
-msgstr "����������/������������ �����"
+msgstr "Технологии/Естественные науки"
 
 msgid "Content$Medicine/Physiology/Psychology"
-msgstr "��������/����������/����������"
+msgstr "Медицина/Физиология/Психология"
 
 msgid "Content$Foreign Countries/Expeditions"
-msgstr "������/����������"
+msgstr "Страны/Экспедиции"
 
 msgid "Content$Social/Spiritual Sciences"
-msgstr "����������/������������� �����"
+msgstr "Социальное/Спиритуальные науки"
 
 msgid "Content$Further Education"
-msgstr "��������� ������������"
+msgstr "Повышение квалификации"
 
 msgid "Content$Languages"
-msgstr "�����"
+msgstr "Языки"
 
 msgid "Content$Leisure/Hobbies"
-msgstr "��������� �����/�����"
+msgstr "Свободное время/Хобби"
 
 msgid "Content$Tourism/Travel"
-msgstr "������/�����������"
+msgstr "Туризм/Путешествия"
 
 msgid "Content$Handicraft"
-msgstr "�������"
+msgstr "Ремесло"
 
 msgid "Content$Motoring"
-msgstr "�����"
+msgstr "Мотор"
 
 msgid "Content$Fitness & Health"
-msgstr "������ � ��������"
+msgstr "Фитнес и здоровье"
 
 msgid "Content$Cooking"
-msgstr "���������"
+msgstr "Кулинария"
 
 msgid "Content$Advertisement/Shopping"
-msgstr "�������/�������"
+msgstr "Реклама/Покупки"
 
 msgid "Content$Gardening"
-msgstr "�����������"
+msgstr "Садоводство"
 
 msgid "Content$Original Language"
-msgstr "������������ ����"
+msgstr "Оригинальный язык"
 
 msgid "Content$Black & White"
-msgstr "����������"
+msgstr "Чернобелое"
 
 msgid "Content$Unpublished"
-msgstr "����������������"
+msgstr "Неопубликованное"
 
 msgid "Content$Live Broadcast"
-msgstr "������ �������"
+msgstr "Прямое вещание"
 
 #, c-format
 msgid "ParentalRating$from %d"
-msgstr "�� %d"
+msgstr "от %d"
 
 msgid "No title"
-msgstr "��� ��������"
+msgstr "Без названия"
 
 #. TRANSLATORS: The name of the language, as written natively
 msgid "LanguageName$English"
-msgstr "�������"
+msgstr "Русский"
 
 #. TRANSLATORS: The 3-letter code of the language
 msgid "LanguageCode$eng"
 msgstr "rus"
 
 msgid "Phase 1: Detecting RC code type"
-msgstr "��� 1: ����������� ���� ���� ������"
+msgstr "Шаг 1: Определение типа кода пульта"
 
 msgid "Press any key on the RC unit"
-msgstr "������� ����� ������ �� ������"
+msgstr "Нажмите любую кнопку на пульте"
 
 msgid "RC code detected!"
-msgstr "��������� ��� ������!"
+msgstr "Обнаружен код пульта!"
 
 msgid "Do not press any key..."
-msgstr "�� ��������� ������..."
+msgstr "Не нажимайте кнопки..."
 
 msgid "Phase 2: Learning specific key codes"
-msgstr "��� 2: ������� ����� ��������� ������"
+msgstr "Шаг 2: Задание кодов отдельных кнопок"
 
 #, c-format
 msgid "Press key for '%s'"
-msgstr "������� ������ '%s'"
+msgstr "Нажмите кнопку '%s'"
 
 msgid "Press 'Up' to confirm"
-msgstr "������� '�����' ����� �����������"
+msgstr "Нажмите 'Вверх' чтобы подтвердить"
 
 msgid "Press 'Down' to continue"
-msgstr "������� '����' ����� ����������"
+msgstr "Нажмите 'Вниз' чтобы продолжить"
 
 msgid "(press 'Up' to go back)"
-msgstr "(������� '�����' ����� ���������)"
+msgstr "(Нажмите 'Вверх' чтобы вернуться)"
 
 msgid "(press 'Down' to end key definition)"
-msgstr "(������� '����' ����� ��������� ��������� ������)"
+msgstr "(Нажмите 'Вниз' чтобы закончить настройку пульта)"
 
 msgid "(press 'Menu' to skip this key)"
-msgstr "(������� '����' ����� ���������� ������)"
+msgstr "(Нажмите 'Меню' чтобы пропустить кнопку)"
 
 msgid "Learning Remote Control Keys"
-msgstr "�������� ������"
+msgstr "Обучение пульта"
 
 msgid "Phase 3: Saving key codes"
-msgstr "��� 3: ����������� ����� ������"
+msgstr "Шаг 3: Запоминание кодов кнопок"
 
 msgid "Press 'Up' to save, 'Down' to cancel"
-msgstr "������� '�����' ����� ���������, '����' ����� ����������"
+msgstr "Нажмите 'Вверх' чтобы запомнить, 'Вниз' чтобы отказаться"
 
 msgid "Key$Up"
-msgstr "�����"
+msgstr "Вверх"
 
 msgid "Key$Down"
-msgstr "����"
+msgstr "Вниз"
 
 msgid "Key$Menu"
-msgstr "����"
+msgstr "Меню"
 
 msgid "Key$Ok"
-msgstr "Ok"
+msgstr "Ок"
 
 msgid "Key$Back"
-msgstr "�����"
+msgstr "Назад"
 
 msgid "Key$Left"
-msgstr "������"
+msgstr "Налево"
 
 msgid "Key$Right"
-msgstr "�������"
+msgstr "Направо"
 
 msgid "Key$Red"
-msgstr "�������"
+msgstr "Красный"
 
 msgid "Key$Green"
-msgstr "�������"
+msgstr "Зелёный"
 
 msgid "Key$Yellow"
-msgstr "������"
+msgstr "Жёлтый"
 
 msgid "Key$Blue"
-msgstr "�����"
+msgstr "Синий"
 
 msgid "Key$Info"
-msgstr "����"
+msgstr "Инфо"
 
 msgid "Key$Play/Pause"
-msgstr "���������������/�����"
+msgstr "Воспроизведение/Пауза"
 
 msgid "Key$Play"
-msgstr "���������������"
+msgstr "Воспроизведение"
 
 msgid "Key$Pause"
-msgstr "�����"
+msgstr "Пауза"
 
 msgid "Key$Stop"
-msgstr "����"
+msgstr "Стоп"
 
 msgid "Key$Record"
-msgstr "������"
+msgstr "Запись"
 
 msgid "Key$FastFwd"
-msgstr "��������� ������"
+msgstr "Прокрутка вперёд"
 
 msgid "Key$FastRew"
-msgstr "��������� �����"
+msgstr "Прокрутка назад"
 
 msgid "Key$Next"
-msgstr "������"
+msgstr "Вперед"
 
 msgid "Key$Prev"
-msgstr "�����"
+msgstr "Назад"
 
 msgid "Key$Power"
-msgstr "���������"
+msgstr "Выключить"
 
 msgid "Key$Channel+"
-msgstr "����� +"
+msgstr "Канал +"
 
 msgid "Key$Channel-"
-msgstr "����� -"
+msgstr "Канал -"
 
 msgid "Key$PrevChannel"
-msgstr "���������� �����"
+msgstr "Предыдущий канал"
 
 msgid "Key$Volume+"
-msgstr "��������� +"
+msgstr "Громкость +"
 
 msgid "Key$Volume-"
-msgstr "��������� -"
+msgstr "Громкость -"
 
 msgid "Key$Mute"
-msgstr "��������� ����"
+msgstr "Выключить звук"
 
 msgid "Key$Audio"
-msgstr "����"
+msgstr "Язык"
 
 msgid "Key$Subtitles"
-msgstr "��������"
+msgstr "Субтитры"
 
 msgid "Key$Schedule"
-msgstr "�������"
+msgstr "Телегид"
 
 msgid "Key$Channels"
-msgstr "������"
+msgstr "Каналы"
 
 msgid "Key$Timers"
-msgstr "�������"
+msgstr "Таймеры"
 
 msgid "Key$Recordings"
-msgstr "������"
+msgstr "Записи"
 
 msgid "Key$Setup"
-msgstr "���������"
+msgstr "Настройка"
 
 msgid "Key$Commands"
-msgstr "�������"
+msgstr "Команды"
 
 msgid "Key$User0"
-msgstr "������������0"
+msgstr "Пользователь0"
 
 msgid "Key$User1"
-msgstr "������������1"
+msgstr "Пользователь1"
 
 msgid "Key$User2"
-msgstr "������������2"
+msgstr "Пользователь2"
 
 msgid "Key$User3"
-msgstr "������������3"
+msgstr "Пользователь3"
 
 msgid "Key$User4"
-msgstr "������������4"
+msgstr "Пользователь4"
 
 msgid "Key$User5"
-msgstr "������������5"
+msgstr "Пользователь5"
 
 msgid "Key$User6"
-msgstr "������������6"
+msgstr "Пользователь6"
 
 msgid "Key$User7"
-msgstr "������������7"
+msgstr "Пользователь7"
 
 msgid "Key$User8"
-msgstr "������������8"
+msgstr "Пользователь8"
 
 msgid "Key$User9"
-msgstr "������������9"
+msgstr "Пользователь9"
 
 msgid "Free To Air"
-msgstr "FTA (��������������)"
+msgstr "FTA (незакодировано)"
 
 msgid "encrypted"
-msgstr "������������"
+msgstr "закодировано"
 
 msgid "Edit channel"
-msgstr "�������������� ������"
+msgstr "Редактирование канала"
 
 msgid "Name"
-msgstr "��������"
+msgstr "Название"
 
 msgid "Source"
-msgstr "��������"
+msgstr "Источник"
 
 msgid "Frequency"
-msgstr "�������"
+msgstr "Частота"
 
 msgid "Vpid"
-msgstr "Vpid (�����)"
+msgstr "Vpid (видео)"
 
 msgid "Ppid"
 msgstr "Ppid"
 
 msgid "Apid1"
-msgstr "Apid1 (����� 1)"
+msgstr "Apid1 (аудио 1)"
 
 msgid "Apid2"
-msgstr "Apid2 (����� 2)"
+msgstr "Apid2 (аудио 2)"
 
 msgid "Dpid1"
 msgstr "Dpid1 (AC3 1)"
@@ -562,754 +562,754 @@ msgid "Dpid2"
 msgstr "Dpid2 (AC3 2)"
 
 msgid "Spid1"
-msgstr "�������� ���1"
+msgstr "Субтитры ПИД1"
 
 msgid "Spid2"
-msgstr "�������� ���2"
+msgstr "Субтитры ПИД2"
 
 msgid "Tpid"
-msgstr "Tpid (���������)"
+msgstr "Tpid (телетекст)"
 
 msgid "CA"
-msgstr "CA (�������)"
+msgstr "CA (модуль доступа)"
 
 msgid "Sid"
 msgstr "Sid"
 
 msgid "Nid"
-msgstr ""
+msgstr "Nid"
 
 msgid "Tid"
-msgstr ""
+msgstr "Tid"
 
 msgid "Channel settings are not unique!"
-msgstr "��������� ������ �� ���������!"
+msgstr "Настройки канала не уникальны!"
 
 msgid "Channels"
-msgstr "������"
+msgstr "Каналы"
 
 msgid "Button$Edit"
-msgstr "�������������"
+msgstr "Редактировать"
 
 msgid "Button$New"
-msgstr "��������"
+msgstr "Добавить"
 
 msgid "Button$Delete"
-msgstr "�������"
+msgstr "Удалить"
 
 msgid "Button$Mark"
-msgstr "�����������"
+msgstr "Переместить"
 
 msgid "Channel is being used by a timer!"
-msgstr "����� ����� ��������!"
+msgstr "Канал занят таймером!"
 
 msgid "Delete channel?"
-msgstr "������� �����?"
+msgstr "Удалить канал?"
 
 msgid "Edit folder"
-msgstr "�������� ����������"
+msgstr "Редакция директории"
 
 msgid "New folder"
-msgstr "����� ����������"
+msgstr "Новая директория"
 
 msgid "Sub folder"
-msgstr "�������������"
+msgstr "поддиректория"
 
 msgid "Folder name already exists!"
-msgstr "���������� ��� ����������!"
+msgstr "Директория уже существует!"
 
 #, c-format
 msgid "Folder name must not contain '%c'!"
-msgstr "��� ���������� �� ������ ��������� '%c'!"
+msgstr "Имя директории не должно содержать '%c'!"
 
 msgid "Button$Open"
-msgstr "�������"
+msgstr "Открыть"
 
 msgid "Delete folder and all sub folders?"
-msgstr "������� ���������� � ��� �������������?"
+msgstr "Удалить директорию и все поддиректории?"
 
 msgid "Delete folder?"
-msgstr "������� ����������?"
+msgstr "Удалить директорию?"
 
 msgid "Edit timer"
-msgstr "��������� �������"
+msgstr "Установка таймера"
 
 msgid "Active"
-msgstr "�����������"
+msgstr "Активирован"
 
 msgid "Channel"
-msgstr "�����"
+msgstr "Канал"
 
 msgid "Day"
-msgstr "����"
+msgstr "День"
 
 msgid "Start"
-msgstr "������"
+msgstr "Начало"
 
 msgid "Stop"
-msgstr "�����"
+msgstr "Конец"
 
 msgid "VPS"
-msgstr "VPS ��������"
+msgstr "VPS поправка"
 
 msgid "Priority"
-msgstr "���������"
+msgstr "Приоритет"
 
 msgid "Lifetime"
-msgstr "���� ��������"
+msgstr "Срок хранения"
 
 msgid "File"
-msgstr "����"
+msgstr "Файл"
 
 msgid "Record on"
-msgstr ""
+msgstr "Запись вкл."
 
 msgid "Button$Folder"
-msgstr "����������"
+msgstr "Директория"
 
 msgid "Button$Single"
-msgstr "���� ���"
+msgstr "Один раз"
 
 msgid "Button$Repeating"
-msgstr "������"
+msgstr "Повтор"
 
 msgid "First day"
-msgstr "������ ����"
+msgstr "Первый день"
 
 msgid "Error while accessing remote timer"
-msgstr ""
+msgstr "Ошибка доступа к таймеру"
 
 msgid "Timer has been deleted!"
-msgstr ""
+msgstr "Таймер был удалён!"
 
 msgid "Select folder"
-msgstr "����� ����������"
+msgstr "Выбор директории"
 
 msgid "Timers"
-msgstr "�������"
+msgstr "Таймеры"
 
 msgid "Button$On/Off"
-msgstr "���/����"
+msgstr "Вкл/Выкл"
 
 msgid "Button$Info"
-msgstr "����"
+msgstr "Инфо"
 
 msgid "Delete timer?"
-msgstr "������� ������?"
+msgstr "Удалить таймер?"
 
 msgid "Timer still recording - really delete?"
-msgstr "���� ������ �� ������� - ������������� �������?"
+msgstr "Идёт запись по таймеру - действительно удалить?"
 
 msgid "Event"
-msgstr "��������"
+msgstr "Передача"
 
 msgid "Button$Timer"
-msgstr "������"
+msgstr "Таймер"
 
 msgid "Button$Record"
-msgstr "������"
+msgstr "Запись"
 
 msgid "Button$Switch"
-msgstr "�����������"
+msgstr "Переключить"
 
 msgid "What's on now?"
-msgstr "������ � �����:"
+msgstr "Сейчас в эфире:"
 
 msgid "What's on next?"
-msgstr "����� � ���������:"
+msgstr "Далее в программе:"
 
 msgid "Button$Next"
-msgstr "�����"
+msgstr "Далее"
 
 msgid "Button$Now"
-msgstr "������"
+msgstr "Сейчас"
 
 msgid "Button$Schedule"
-msgstr "���������"
+msgstr "Программа"
 
 msgid "Can't switch channel!"
-msgstr "���������� ����������� �����!"
+msgstr "Невозможно переключить канал!"
 
 #, c-format
 msgid "Schedule - %s"
-msgstr "��������� - %s"
+msgstr "Программа - %s"
 
 #, c-format
 msgid "This event - %s"
-msgstr "��� �������� - %s"
+msgstr "Эта передача - %s"
 
 msgid "This event - all channels"
-msgstr "��� �������� - ��� ������"
+msgstr "Эта передача - все каналы"
 
 msgid "All events - all channels"
-msgstr "��� �������� - ��� ������"
+msgstr "Все передачи - все каналы"
 
 #, c-format
 msgid "Please enter %d digits!"
-msgstr "������� %d �����"
+msgstr "Нажмите %d цифры"
 
 msgid "CAM not responding!"
-msgstr "CAM �� ��������"
+msgstr "CAM не отвечает"
 
 msgid "Edit path"
-msgstr ""
+msgstr "Изменить путь"
 
 msgid "Folder"
-msgstr ""
+msgstr "Папка"
 
 msgid "This folder is currently in use - no changes are possible!"
-msgstr ""
+msgstr "Папка используется - не изменено!"
 
 #, c-format
 msgid "Move entire folder containing %d recordings?"
-msgstr ""
+msgstr "Переместить папку с %d записями?"
 
 msgid "Error while moving folder!"
-msgstr ""
+msgstr "Ошибка перемещения папки!"
 
 msgid "Edit recording"
-msgstr ""
+msgstr "Изменить запись"
 
 msgid "This recording is currently in use - no changes are possible!"
-msgstr ""
+msgstr "Запись используется - изменение не возможно!"
 
 msgid "Button$Cancel cutting"
-msgstr ""
+msgstr "Отмена обрезки"
 
 msgid "Button$Stop cutting"
-msgstr ""
+msgstr "Останов. обрезку"
 
 msgid "Button$Cancel moving"
-msgstr ""
+msgstr "Отмена перемещения"
 
 msgid "Button$Stop moving"
-msgstr ""
+msgstr "Останов. перемещение"
 
 msgid "Button$Cancel copying"
-msgstr ""
+msgstr "Отмена копирования"
 
 msgid "Button$Stop copying"
-msgstr ""
+msgstr "Останов. копирование"
 
 msgid "Button$Cut"
-msgstr ""
+msgstr "Обрезка"
 
 msgid "Button$Delete marks"
-msgstr ""
+msgstr "Удалить метки"
 
 msgid "Recording vanished!"
-msgstr ""
+msgstr "Запись исчезла!"
 
 msgid "Edited version already exists - overwrite?"
-msgstr ""
+msgstr "Изменённая версия уже есть - переписать?"
 
 msgid "Error while queueing recording for cutting!"
-msgstr ""
+msgstr "Ошибка очереди записи для обрезки!"
 
 msgid "Rename recording to folder name?"
-msgstr ""
+msgstr "Переименовать запись в имя папки?"
 
 msgid "Delete editing marks for this recording?"
-msgstr ""
+msgstr "Удалить метки для этой записи?"
 
 msgid "Error while deleting editing marks!"
-msgstr ""
+msgstr "Ошибка удаления меток!"
 
 msgid "Error while changing priority/lifetime!"
-msgstr ""
+msgstr "Ошибка изменения приоритета/длительности"
 
 msgid "Error while changing folder/name!"
-msgstr ""
+msgstr "Ошибка изменения папки/имени!"
 
 msgid "Recording info"
-msgstr "���� � ������"
+msgstr "Инфо о записи"
 
 msgid "Button$Play"
-msgstr "�������������"
+msgstr "Воспроизвести"
 
 msgid "Button$Rewind"
-msgstr "�����"
+msgstr "Назад"
 
 msgid "Recordings"
-msgstr "������"
+msgstr "Записи"
 
 msgid "Commands"
-msgstr "�������"
+msgstr "Команды"
 
 msgid "Delete recording?"
-msgstr "������� ������?"
+msgstr "Стереть запись?"
 
 msgid "Recording is being edited - really delete?"
-msgstr "������ �������� - ������������� �������?"
+msgstr "Запись изменена - действительно удалить?"
 
 msgid "Error while deleting recording!"
-msgstr "������ �������� ������!"
+msgstr "Ошибка удаления записи!"
 
 msgid "Recording commands"
-msgstr "������� ������"
+msgstr "Команды записи"
 
 msgid "never"
-msgstr "�������"
+msgstr "никогда"
 
 msgid "skin dependent"
-msgstr "�������� �����"
+msgstr "согласно стиля"
 
 msgid "always"
-msgstr "������"
+msgstr "всегда"
 
 msgid "by name"
-msgstr ""
+msgstr "по имени"
 
 msgid "by time"
-msgstr ""
+msgstr "по времени"
 
 msgid "OSD"
-msgstr "����"
+msgstr "Меню"
 
 msgid "Setup.OSD$Language"
-msgstr "����"
+msgstr "Язык"
 
 msgid "Setup.OSD$Skin"
-msgstr "�����"
+msgstr "Стиль"
 
 msgid "Setup.OSD$Theme"
-msgstr "����"
+msgstr "Тема"
 
 msgid "Setup.OSD$Left (%)"
-msgstr "������ ����� (%)"
+msgstr "Отступ слева (%)"
 
 msgid "Setup.OSD$Top (%)"
-msgstr "������ ������ (%)"
+msgstr "Отступ сверху (%)"
 
 msgid "Setup.OSD$Width (%)"
-msgstr "������ (%)"
+msgstr "Ширина (%)"
 
 msgid "Setup.OSD$Height (%)"
-msgstr "������ (%)"
+msgstr "Высота (%)"
 
 msgid "Setup.OSD$Message time (s)"
-msgstr "������������ ������ ��������� (���)"
+msgstr "Длительность показа сообщений (сек)"
 
 msgid "Setup.OSD$Use small font"
-msgstr "������������ ������ �����"
+msgstr "Использовать мелкий шрифт"
 
 msgid "Setup.OSD$Anti-alias"
-msgstr "����������� �������"
+msgstr "Сглаживание шрифтов"
 
 msgid "Setup.OSD$Default font"
-msgstr "����������� ����"
+msgstr "Стандартный шрифт"
 
 msgid "Setup.OSD$Small font"
-msgstr "������ ����"
+msgstr "Мелкий шрифт"
 
 msgid "Setup.OSD$Fixed font"
-msgstr "������������� ����"
+msgstr "Фиксированный шрифт"
 
 msgid "Setup.OSD$Default font size (%)"
-msgstr "������ ����� ��� ���� (%)"
+msgstr "Размер шрифта для меню (%)"
 
 msgid "Setup.OSD$Small font size (%)"
-msgstr "������ ������� ����� (%)"
+msgstr "Размер мелкого шрифта (%)"
 
 msgid "Setup.OSD$Fixed font size (%)"
-msgstr "������ �������������� ����� (%)"
+msgstr "Размер фиксированного шрифта (%)"
 
 msgid "Setup.OSD$Channel info position"
-msgstr "��������� ���� ���������� � ������"
+msgstr "Положение окна информации о канале"
 
 msgid "bottom"
-msgstr "�����"
+msgstr "снизу"
 
 msgid "top"
-msgstr "������"
+msgstr "сверху"
 
 msgid "Setup.OSD$Channel info time (s)"
-msgstr "����� ���������� � ������ (���)"
+msgstr "Показ информации о канале (сек)"
 
 msgid "Setup.OSD$Info on channel switch"
-msgstr "���������� ���������� � ������"
+msgstr "Показывать информацию о канале"
 
 msgid "Setup.OSD$Timeout requested channel info"
-msgstr "���������� � ������ �������"
+msgstr "Информацию о канале закрыть"
 
 msgid "Setup.OSD$Scroll pages"
-msgstr "��������� ������� ����"
+msgstr "Прокрутка страниц меню"
 
 msgid "Setup.OSD$Scroll wraps"
-msgstr "����������� ���������"
+msgstr "Циклическая прокрутка"
 
 msgid "Setup.OSD$Menu key closes"
-msgstr "������ ���� �������"
+msgstr "Кнопку Меню закрыть"
 
 msgid "Setup.OSD$Recording directories"
-msgstr "�������� �������� �������"
+msgstr "Каталоги хранения записей"
 
 msgid "Setup.OSD$Folders in timer menu"
-msgstr "���������� � ���� ������"
+msgstr "Директории в меню таймер"
 
 msgid "Setup.OSD$Always sort folders first"
-msgstr "���������� ������ � ������ ������� �����������"
+msgstr "Директории всегда в первую очередь сортировать"
 
 msgid "Setup.OSD$Default sort mode for recordings"
-msgstr ""
+msgstr "Сортировка по умолчанию"
 
 msgid "Setup.OSD$Number keys for characters"
-msgstr "���������� ������ ��� ��������"
+msgstr "Количество кнопок для символов"
 
 msgid "Setup.OSD$Color key 0"
-msgstr "������� ������ 0"
+msgstr "Цветная кнопка 0"
 
 msgid "Setup.OSD$Color key 1"
-msgstr "������� ������ 1"
+msgstr "Цветная кнопка 1"
 
 msgid "Setup.OSD$Color key 2"
-msgstr "������� ������ 2"
+msgstr "Цветная кнопка 2"
 
 msgid "Setup.OSD$Color key 3"
-msgstr "������� ������ 3"
+msgstr "Цветная кнопка 3"
 
 msgid "EPG"
-msgstr "�������"
+msgstr "Телегид"
 
 msgid "Button$Scan"
-msgstr "�����������"
+msgstr "Сканировать"
 
 msgid "Setup.EPG$EPG scan timeout (h)"
-msgstr "�������� ������������ �������� (�)"
+msgstr "Задержка сканирования телегида (ч)"
 
 msgid "Setup.EPG$EPG bugfix level"
-msgstr "������� ��������� ������"
+msgstr "Уровень коррекции ошибок"
 
 msgid "Setup.EPG$EPG linger time (min)"
-msgstr "�������� ���������� ������ (���)"
+msgstr "Хранение устаревших данных (мин)"
 
 msgid "Setup.EPG$Set system time"
-msgstr "���������� ��������� �����"
+msgstr "Установить системное время"
 
 msgid "Setup.EPG$Use time from transponder"
-msgstr "������������ ����� ������������"
+msgstr "Использовать время транспондера"
 
 #. TRANSLATORS: note the plural!
 msgid "Setup.EPG$Preferred languages"
-msgstr "�������������� ����� (�������)"
+msgstr "Предпочитаемые языки (телегид)"
 
 #. TRANSLATORS: note the singular!
 msgid "Setup.EPG$Preferred language"
-msgstr "������"
+msgstr "Выбранный язык"
 
 msgid "pan&scan"
-msgstr "��������������"
+msgstr "панорамировать"
 
 msgid "letterbox"
-msgstr "���������"
+msgstr "уменьшать"
 
 msgid "center cut out"
-msgstr "�������� �����"
+msgstr "обрезать сбоку"
 
 msgid "no"
-msgstr "���"
+msgstr "нет"
 
 msgid "names only"
-msgstr "������ ��������"
+msgstr "только названия"
 
 msgid "PIDs only"
-msgstr "������ PID�"
+msgstr "Только PIDы"
 
 msgid "names and PIDs"
-msgstr "�������� � PID�"
+msgstr "названия и PIDы"
 
 msgid "add new channels"
-msgstr "����� ������"
+msgstr "новые каналы"
 
 msgid "add new transponders"
-msgstr "���. ������������"
+msgstr "нов. транспондеры"
 
 msgid "DVB"
 msgstr "DVB"
 
 msgid "Button$Audio"
-msgstr "����"
+msgstr "Язык"
 
 msgid "Button$Subtitles"
-msgstr "��������"
+msgstr "Субтитры"
 
 msgid "Setup.DVB$Primary DVB interface"
-msgstr "�������� DVB-����������"
+msgstr "Основное DVB-устройство"
 
 msgid "Setup.DVB$Standard compliance"
-msgstr "����������� ���������"
+msgstr "Стандартное поведение"
 
 msgid "Setup.DVB$Video format"
-msgstr "������ �����"
+msgstr "Формат видео"
 
 msgid "Setup.DVB$Video display format"
-msgstr "�������������� �����������"
+msgstr "Широкоэкранное изображение"
 
 msgid "Setup.DVB$Use Dolby Digital"
-msgstr "�������� Dolby Digital"
+msgstr "Включить Dolby Digital"
 
 msgid "Setup.DVB$Update channels"
-msgstr "��������� ��������� �������"
+msgstr "Обновлять настройки каналов"
 
 msgid "Setup.DVB$Audio languages"
-msgstr "�������������� ����� (����)"
+msgstr "Предпочитаемые языки (звук)"
 
 msgid "Setup.DVB$Audio language"
-msgstr "������"
+msgstr "Выбран"
 
 msgid "Setup.DVB$Display subtitles"
-msgstr "���������� ��������"
+msgstr "Показывать субтитры"
 
 msgid "Setup.DVB$Subtitle languages"
-msgstr "����� ���������"
+msgstr "Языки субтитров"
 
 msgid "Setup.DVB$Subtitle language"
-msgstr "���� ���������"
+msgstr "Язык субтитров"
 
 msgid "Setup.DVB$Subtitle offset"
-msgstr "����� ���������"
+msgstr "Сдвиг субтитров"
 
 msgid "Setup.DVB$Subtitle foreground transparency"
-msgstr "������������ ��������� ����� ���������"
+msgstr "Прозрачность переднего плана субтитров"
 
 msgid "Setup.DVB$Subtitle background transparency"
-msgstr "������������ ���� ���������"
+msgstr "Прозрачность фона субтитров"
 
 msgid "LNB"
-msgstr "���������"
+msgstr "Конвертер"
 
 msgid "Setup.LNB$Use DiSEqC"
-msgstr "������������ DiSEqC"
+msgstr "Использовать DiSEqC"
 
 msgid "Setup.LNB$SLOF (MHz)"
-msgstr "������� ������������ (SLOF) (���)"
+msgstr "Частота переключения (SLOF) (МГц)"
 
 msgid "Setup.LNB$Low LNB frequency (MHz)"
-msgstr "������ ������� ���������� (���)"
+msgstr "Нижняя частота конвертера (МГц)"
 
 msgid "Setup.LNB$High LNB frequency (MHz)"
-msgstr "������� ������� ���������� (���)"
+msgstr "Верхняя частота конвертера (МГц)"
 
 #, c-format
 msgid "Setup.LNB$Device %d connected to sat cable"
-msgstr "���������� %d ����������� � ���������"
+msgstr "Устройство %d подключеное к саткабелю"
 
 msgid "Setup.LNB$own"
-msgstr "����"
+msgstr "свой"
 
 msgid "Setup.LNB$Use dish positioner"
-msgstr ""
+msgstr "Использовать позиционер"
 
 msgid "Setup.LNB$Site latitude (degrees)"
-msgstr ""
+msgstr "Широта (град.)"
 
 msgid "South"
-msgstr ""
+msgstr "Юг"
 
 msgid "North"
-msgstr ""
+msgstr "Север"
 
 msgid "Setup.LNB$Site longitude (degrees)"
-msgstr ""
+msgstr "Долгота (град.)"
 
 msgid "West"
-msgstr ""
+msgstr "Запад"
 
 msgid "East"
-msgstr ""
+msgstr "Восток"
 
 msgid "Setup.LNB$Max. positioner swing (degrees)"
-msgstr ""
+msgstr "Макс ход позиционера (град.)"
 
 msgid "Setup.LNB$Positioner speed (degrees/s)"
-msgstr ""
+msgstr "Скорость позиционера (град./сек)"
 
 msgid "CAM reset"
-msgstr "CAM ����������"
+msgstr "CAM сброс"
 
 msgid "CAM present"
-msgstr "CAM ������������"
+msgstr "CAM присутствует"
 
 msgid "CAM ready"
-msgstr "CAM �����"
+msgstr "CAM готов"
 
 #. TRANSLATORS: note the leading blank!
 msgid " (activating)"
-msgstr ""
+msgstr " (запускается)"
 
 msgid "@ device"
-msgstr ""
+msgstr "@ устройство"
 
 msgid "CAM"
-msgstr "�������� ������"
+msgstr "Условный доступ"
 
 msgid "Button$Cancel activation"
-msgstr ""
+msgstr "Отмена запуска"
 
 msgid "Button$Activate"
-msgstr ""
+msgstr "Запуск"
 
 msgid "Button$Menu"
-msgstr "����"
+msgstr "Меню"
 
 msgid "Button$Reset"
-msgstr "�����"
+msgstr "Сброс"
 
 msgid "Opening CAM menu..."
-msgstr "�������� ���� ������ ��������� ������� (CAM)"
+msgstr "Открываю меню модуля условного доступа (CAM)"
 
 msgid "Can't open CAM menu!"
-msgstr "���� CAM-������ ����������!"
+msgstr "Меню CAM-модуля недоступно!"
 
 msgid "Can't activate CAM!"
-msgstr ""
+msgstr "CAM не запущен!"
 
 msgid "CAM is in use - really reset?"
-msgstr "CAM ������������ - ������������� �����������?"
+msgstr "CAM используется - действительно перегрузить?"
 
 msgid "Can't reset CAM!"
-msgstr "������ ����������� CAM-������!"
+msgstr "Ошибка перезапуска CAM-модуля!"
 
 msgid "no instant recording"
-msgstr ""
+msgstr "нет быстрой записи"
 
 msgid "confirm instant recording"
-msgstr ""
+msgstr "подтв. быструю запись"
 
 msgid "record instantly"
-msgstr ""
+msgstr "быстрая запись"
 
 msgid "do not pause live video"
-msgstr "�� ������������� live video"
+msgstr "не останавливать live video"
 
 msgid "confirm pause live video"
-msgstr "������������� ��������� live video"
+msgstr "подтверждение остановки live video"
 
 msgid "pause live video"
-msgstr "��������� live video"
+msgstr "остановка live video"
 
 msgid "confirm"
-msgstr "�������������"
+msgstr "подтверждение"
 
 msgid "yes"
-msgstr "��"
+msgstr "да"
 
 msgid "Recording"
-msgstr "������"
+msgstr "Запись"
 
 msgid "Setup.Recording$Margin at start (min)"
-msgstr "���������� ������ ������ (���)"
+msgstr "Опережение начала записи (мин)"
 
 msgid "Setup.Recording$Margin at stop (min)"
-msgstr "������������ ��������� ������ (���)"
+msgstr "Запаздывание остановки записи (мин)"
 
 msgid "Setup.Recording$Default priority"
-msgstr "��������� ������� �� ���������"
+msgstr "Приоритет таймера по умолчанию"
 
 msgid "Setup.Recording$Default lifetime (d)"
-msgstr "���� �������� ������ �� ��������� (�)"
+msgstr "Срок хранения записи по умолчанию (д)"
 
 msgid "Setup.Recording$Record key handling"
-msgstr ""
+msgstr "Функция кнопки запись"
 
 msgid "Setup.Recording$Pause key handling"
-msgstr "������� ������ �����"
+msgstr "Функция кнопки пауза"
 
 msgid "Setup.Recording$Pause priority"
-msgstr "��������� ����������� ���������"
+msgstr "Приоритет отложенного просмотра"
 
 msgid "Setup.Recording$Pause lifetime (d)"
-msgstr "�������� ����������� ��������� (�)"
+msgstr "Хранение отложенного просмотра (д)"
 
 msgid "Setup.Recording$Use episode name"
-msgstr "������������ ����� �� ��������"
+msgstr "Группировать файлы по эпизодам"
 
 msgid "Setup.Recording$Use VPS"
-msgstr "������������ ������� VPS"
+msgstr "Использовать сигналы VPS"
 
 msgid "Setup.Recording$VPS margin (s)"
-msgstr "�������� ����� VPS (���)"
+msgstr "Буферное время VPS (сек)"
 
 msgid "Setup.Recording$Mark instant recording"
-msgstr "�������� ��������� ������� ������"
+msgstr "Отмечать быстрые записи"
 
 msgid "Setup.Recording$Name instant recording"
-msgstr "����� ���������� ������ �������"
+msgstr "Имя быстрой записи"
 
 msgid "Setup.Recording$Instant rec. time (min)"
-msgstr "������������ ������ ������ (���)"
+msgstr "Длительность быстрой записи (мин)"
 
 msgid "Setup.Recording$present event"
-msgstr "������� ��������"
+msgstr "текущая передача"
 
 msgid "Setup.Recording$Max. video file size (MB)"
-msgstr "����. ������ ���������� (��)"
+msgstr "Макс. размер видеофайла (Мб)"
 
 msgid "Setup.Recording$Split edited files"
-msgstr "������ ����������������� �����"
+msgstr "Делить отредактированные файлы"
 
 msgid "Setup.Recording$Delete timeshift recording"
-msgstr "�������� ���������� ������"
+msgstr "удаления отложенной записи"
 
 msgid "Replay"
-msgstr "���������������"
+msgstr "Воспроизведение"
 
 msgid "Setup.Replay$Multi speed mode"
-msgstr "��������������� �����"
+msgstr "Многоскоростной режим"
 
 msgid "Setup.Replay$Show replay mode"
-msgstr "���������� ����� ���������������"
+msgstr "Отображать режим воспроизведения"
 
 msgid "Setup.Replay$Show remaining time"
-msgstr "����� ����������� �������"
+msgstr "Показ оставшегося времени"
 
 msgid "Setup.Replay$Progress display time (s)"
-msgstr "����� ��������� (s)"
+msgstr "Показ прогресса (s)"
 
 msgid "Setup.Replay$Pause replay when setting mark"
-msgstr "����� ��� ��������� ����������"
+msgstr "Пауза при установке маркировки"
 
 msgid "Setup.Replay$Pause replay when jumping to a mark"
-msgstr ""
+msgstr "Пауза после перемещения к метке"
 
 msgid "Setup.Replay$Skip edited parts"
-msgstr ""
+msgstr "Пропуск изменённой части"
 
 msgid "Setup.Replay$Pause replay at last mark"
-msgstr ""
+msgstr "Пауза на последней метке"
 
 msgid "Setup.Replay$Initial duration for adaptive skipping (s)"
-msgstr ""
+msgstr "Начальный интервал адаптивного пропуска (сек)"
 
 msgid "Setup.Replay$Reset timeout for adaptive skipping (s)"
-msgstr ""
+msgstr "Сброс ожидания адаптивного пропуска (сек)"
 
 msgid "Setup.Replay$Alternate behavior for adaptive skipping"
-msgstr ""
+msgstr "Другое поведение адаптивного пропуска"
 
 msgid "Setup.Replay$Use Prev/Next keys for adaptive skipping"
-msgstr ""
+msgstr "Использовать Назад/Вперёд для адаптивного поиска"
 
 msgid "Setup.Replay$Skip distance with Green/Yellow keys (s)"
-msgstr ""
+msgstr "Пропуск интервала Зелёный/Жёлтый (сек)"
 
 msgid "Setup.Replay$Skip distance with Green/Yellow keys in repeat (s)"
-msgstr ""
+msgstr "Пропуск интервала Зелёный/Жёлтый в повторе (сек)"
 
 msgid "Setup.Replay$Resume ID"
-msgstr "ID ���������������"
+msgstr "ID воспроизведения"
 
 msgid "Miscellaneous"
-msgstr "������"
+msgstr "Прочее"
 
 msgid "Setup.Miscellaneous$Min. event timeout (min)"
-msgstr "���. ����� �������� ������� (���)"
+msgstr "Мин. время ожидания события (мин)"
 
 msgid "Setup.Miscellaneous$Min. user inactivity (min)"
-msgstr "���. ����� �������� ����� (���)"
+msgstr "Мин. время ожидания ввода (мин)"
 
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
-msgstr "�������� ������ ����. SVDRP (���)"
+msgstr "Задержка обрыва соед. SVDRP (сек)"
 
 msgid "Setup.Miscellaneous$SVDRP peering"
 msgstr ""
@@ -1321,288 +1321,288 @@ msgid "Setup.Miscellaneous$SVDRP default host"
 msgstr ""
 
 msgid "Setup.Miscellaneous$Zap timeout (s)"
-msgstr "�������� ������������ ������ (���)"
+msgstr "Задержка переключения канала (сек)"
 
 msgid "Setup.Miscellaneous$Channel entry timeout (ms)"
-msgstr "������ ������� ��� ����� ������ (ms)"
+msgstr "Предел времени для ввода канала (ms)"
 
 msgid "Setup.Miscellaneous$Remote control repeat delay (ms)"
-msgstr "�������� ������ �� ������ (ms)"
+msgstr "Задержка ответа от пульта (ms)"
 
 msgid "Setup.Miscellaneous$Remote control repeat delta (ms)"
-msgstr "�������� ������ �� ������ (ms)"
+msgstr "Интервал ответов от пульта (ms)"
 
 msgid "Setup.Miscellaneous$Initial channel"
-msgstr "����� ��� ���������"
+msgstr "Канал при включении"
 
 msgid "Setup.Miscellaneous$as before"
-msgstr "��� ������"
+msgstr "как раньше"
 
 msgid "Setup.Miscellaneous$Initial volume"
-msgstr "��������� ��� ���������"
+msgstr "Громкость при включении"
 
 msgid "Setup.Miscellaneous$Volume steps"
-msgstr ""
+msgstr "Шаг громкости"
 
 msgid "Setup.Miscellaneous$Volume linearize"
-msgstr ""
+msgstr "Линейность громкости"
 
 msgid "Setup.Miscellaneous$Channels wrap"
-msgstr "��������� �������"
+msgstr "прокрутка каналов"
 
 msgid "Setup.Miscellaneous$Show channel names with source"
-msgstr "����� ����� ������ � ���������"
+msgstr "Показ имени канала и источника"
 
 msgid "Setup.Miscellaneous$Emergency exit"
-msgstr "��������� �����"
+msgstr "Аварийный выход"
 
 msgid "Plugins"
-msgstr "������ ����������"
+msgstr "Модули расширения"
 
 msgid "This plugin has no setup parameters!"
-msgstr "������ �� ����� ���������� ���������!"
+msgstr "Модуль не имеет параметров настройки!"
 
 msgid "Setup"
-msgstr "���������"
+msgstr "Настройка"
 
 msgid "Restart"
-msgstr "�������������"
+msgstr "Перезапустить"
 
 msgid "Really restart?"
-msgstr "������������� �������������?"
+msgstr "Действительно перезапустить?"
 
 #. TRANSLATORS: note the leading and trailing blanks!
 msgid " Stop recording "
-msgstr " ���������� ������ "
+msgstr " Прекратить запись "
 
 msgid "Schedule"
-msgstr "�������"
+msgstr "Телегид"
 
 #. TRANSLATORS: note the leading blank!
 msgid " Stop replaying"
-msgstr " ���������� ���������������"
+msgstr " Прекратить воспроизведение"
 
 msgid "Button$Pause"
-msgstr "�����"
+msgstr "Пауза"
 
 msgid "Button$Stop"
-msgstr "����"
+msgstr "Стоп"
 
 msgid "Button$Resume"
-msgstr "����������"
+msgstr "Продолжить"
 
 #. TRANSLATORS: note the leading blank!
 msgid " Cancel editing"
-msgstr " �������� ������ ������"
+msgstr " Прервать монтаж записи"
 
 msgid "Stop recording?"
-msgstr "���������� ������?"
+msgstr "Прекратить запись?"
 
 msgid "Cancel editing?"
-msgstr "�������� ��������������?"
+msgstr "Прервать редактирование?"
 
 msgid "No audio available!"
-msgstr "����������� ����!"
+msgstr "Отсутствует звук!"
 
 msgid "No subtitles"
-msgstr "��� ���������"
+msgstr "Нет субтитров"
 
 msgid "No subtitles available!"
-msgstr "�������� ����������!"
+msgstr "Субтитры недоступны!"
 
 msgid "Not enough disk space to start recording!"
-msgstr "������������ ����� �� ����� ��� ������ ������"
+msgstr "Недостаточно места на диске для начала записи"
 
 msgid "No free DVB device to record!"
-msgstr "��� ���������� DVB-���������� ��� ������!"
+msgstr "Нет свободного DVB-устройства для записи!"
 
 msgid "Pausing live video..."
-msgstr "����� ����������� ���������..."
+msgstr "Режим отложенного просмотра..."
 
 msgid "Delete timeshift recording?"
-msgstr "������� ���������� ������?"
+msgstr "Удалить отложенную запись?"
 
 #. TRANSLATORS: note the trailing blank!
 msgid "Jump: "
-msgstr "�������: "
+msgstr "Перейти: "
 
 msgid "No editing marks defined!"
-msgstr "�� ������ ����� ��� �������!"
+msgstr "Не заданы метки для монтажа!"
 
 msgid "No editing sequences defined!"
-msgstr "������� �������������� �� ���������!"
+msgstr "Порядок редактирования не определен!"
 
 msgid "Can't start editing process!"
-msgstr "���������� ������ ������ ������!"
+msgstr "Невозможно начать монтаж записи!"
 
 msgid "Editing process started"
-msgstr "����� ������ ������"
+msgstr "Начат монтаж записи"
 
 msgid "Editing process already active!"
-msgstr "������� ������������ ��� �������!"
+msgstr "Процесс видеомонтажа уже запущен!"
 
 msgid "FileNameChars$ abcdefghijklmnopqrstuvwxyz0123456789-.,#~\\^$[]|()*+?{}/:%@&"
-msgstr " abcdefghijklmnopqrstuvwxyz��������������������������������0123456789-.,#~\\^$[]|()*+?{}/:%@&"
+msgstr " abcdefghijklmnopqrstuvwxyzабвгдеёжзийклмнопрстуфхцчшщъыьюя0123456789-.,#~\\^$[]|()*+?{}/:%@&"
 
 msgid "CharMap$ 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabc2\tdef3\tghi4\tjkl5\tmno6\tpqrs7\ttuv8\twxyz9"
-msgstr " 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabc����2\tdef�����3\tghi����4\tjkl���5\tmno���6\tpqrs����7\ttuv�����8\twxyz�����9"
+msgstr " 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabcабвг2\tdefдеёжз3\tghiийкл4\tjklмно5\tmnoпрс6\tpqrsтуфх7\ttuvцчшщъ8\twxyzыьэюя9"
 
 msgid "Button$ABC/abc"
-msgstr "���/���"
+msgstr "АБВ/абв"
 
 msgid "Button$Overwrite"
-msgstr "������"
+msgstr "Замена"
 
 msgid "Button$Insert"
-msgstr "�������"
+msgstr "Вставка"
 
 msgid "Plugin"
-msgstr "������"
+msgstr "Модуль"
 
 msgid "Up/Dn for new location - OK to move"
-msgstr "��������� \"�����\"/\"����\" ��� ������ �������, � ����� \"OK\""
+msgstr "Нажимайте \"Вверх\"/\"Вниз\" для выбора позиции, а затем \"OK\""
 
 msgid "Channel locked (recording)!"
-msgstr "����� ������������ (���� ������)!"
+msgstr "Канал заблокирован (идёт запись)!"
 
 msgid "Low disk space!"
-msgstr "������������ ����� �� �����!"
+msgstr "Недостаточно места на диске!"
 
 msgid "Regenerating index file"
-msgstr "����������� ���������� �����"
+msgstr "Регенерация индексного файла"
 
 msgid "Index file regeneration complete"
-msgstr "����������� ���������� ����� ��������"
+msgstr "Регенерация индексного файла окончена"
 
 msgid "Index file regeneration failed!"
-msgstr "������ ����������� ���������� �����"
+msgstr "Ошибка регенерация индексного файла!"
 
 msgid "Can't shutdown - option '-s' not given!"
-msgstr "���������� ���������� - �� ����� �������� '-s'!"
+msgstr "Выключение невозможно - не задан параметр '-s'!"
 
 msgid "Editing - shut down anyway?"
-msgstr "���� ������ - ������������� ���������?"
+msgstr "Идет монтаж - действительно выключить?"
 
 msgid "Recording - shut down anyway?"
-msgstr "���� ������ - ������������� ���������?"
+msgstr "Идёт запись - действительно выключить?"
 
 #, c-format
 msgid "Recording in %ld minutes, shut down anyway?"
-msgstr "����� %ld ����� �������� ������ - ������������� ���������?"
+msgstr "Через %ld минут начнётся запись - действительно выключить?"
 
 msgid "shut down anyway?"
-msgstr "������������� ���������?"
+msgstr "действительно выключить?"
 
 #, c-format
 msgid "Plugin %s wakes up in %ld min, continue?"
-msgstr "Plugin %s ��������� ����� %ld ����� - ����������?"
+msgstr "Plugin %s проснется через %ld минут - продолжить?"
 
 msgid "Editing - restart anyway?"
-msgstr "������� ������� - ������������� �������������?"
+msgstr "Процесс монтажа - действительно перезапустить?"
 
 msgid "Recording - restart anyway?"
-msgstr "���� ������ - ������������� �������������?"
+msgstr "Идёт запись - действительно перезапустить?"
 
 msgid "restart anyway?"
-msgstr "������������� �������������?"
+msgstr "действительно перезапустить?"
 
 #. TRANSLATORS: note the trailing blank!
 msgid "Volume "
-msgstr "��������� "
+msgstr "Громкость "
 
 msgid "Classic VDR"
-msgstr "������������"
+msgstr "Классический"
 
 msgid "DISK"
-msgstr "����"
+msgstr "ДИСК"
 
 msgid "LOAD"
-msgstr "�������������"
+msgstr "ЗАГРУЗКА"
 
 msgid "TIMERS"
-msgstr "�������"
+msgstr "ТФЙМЕРЫ"
 
 msgid "DEVICES"
-msgstr "����������"
+msgstr "УСТРОЙСТВА"
 
 msgid "LIVE"
 msgstr "LIVE"
 
 msgid "PLAY"
-msgstr "���������������"
+msgstr "Воспроизведение"
 
 #, c-format
 msgid "Moving dish to %.1f..."
-msgstr ""
+msgstr "Сдвинуть тарель к %.1f..."
 
 msgid "ST:TNG Panels"
-msgstr "ST:TNG ������"
+msgstr "ST:TNG панели"
 
 #. TRANSLATORS: the first character of each weekday, beginning with monday
 msgid "MTWTFSS"
-msgstr "���ǿ��"
+msgstr "ПВСЧПСВ"
 
 #. TRANSLATORS: abbreviated weekdays, beginning with monday (must all be 3 letters!)
 msgid "MonTueWedThuFriSatSun"
-msgstr "��ݲ�������ҿ����Ѳ��"
+msgstr "ПонВтрСрдЧтвПтнСубВск"
 
 msgid "Monday"
-msgstr "�����������"
+msgstr "Понедельник"
 
 msgid "Tuesday"
-msgstr "�������"
+msgstr "Вторник"
 
 msgid "Wednesday"
-msgstr "�����"
+msgstr "Среда"
 
 msgid "Thursday"
-msgstr "�������"
+msgstr "Четверг"
 
 msgid "Friday"
-msgstr "�������"
+msgstr "Пятница"
 
 msgid "Saturday"
-msgstr "�������"
+msgstr "Суббота"
 
 msgid "Sunday"
-msgstr "�����������"
+msgstr "Воскресенье"
 
 msgid "Upcoming recording!"
-msgstr "������ ����� ��������"
+msgstr "Запись скоро начнется!"
 
 msgid "Pause live video?"
-msgstr "���������� live video?"
+msgstr "Остановить live video?"
 
 msgid "Start recording?"
-msgstr ""
+msgstr "Запустить запись?"
 
 msgid "Recording started"
-msgstr "������ ������"
+msgstr "Запись начата"
 
 msgid "VDR will shut down later - press Power to force"
-msgstr "VDR ���������� ����� - ������� Power ��� ���������"
+msgstr "VDR выключится позже - нажмите Питание для ускорения"
 
 msgid "Press any key to cancel shutdown"
-msgstr "������� ����� ������ ����� �������� ����������."
+msgstr "Нажмите любую кнопку чтобы отменить выключение"
 
 msgid "Switching primary DVB..."
-msgstr "����� ��������� DVB-����������..."
+msgstr "Смена основного DVB-устройства..."
 
 msgid "Editing process failed!"
-msgstr "������ �� ����� ������� ������!"
+msgstr "Ошибка во время монтажа записи!"
 
 msgid "Editing process finished"
-msgstr "������ �������"
+msgstr "Монтаж окончен"
 
 msgid "Press any key to cancel restart"
-msgstr "������� ����� ������ ��� ������ ������������"
+msgstr "Нажмите любую кнопку для отмены перезагрузки"
 
 #, c-format
 msgid "VDR will shut down in %s minutes"
-msgstr "VDR ���������� ����� %s �����"
+msgstr "VDR выключится через %s минут"
 
 msgid "Disk"
-msgstr "����"
+msgstr "Диск"
 
 msgid "free"
-msgstr "��������"
+msgstr "свободно"
diff --git a/receiver.c b/receiver.c
index 8d647c0..dd61fd6 100644
--- a/receiver.c
+++ b/receiver.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: receiver.c 4.1 2015/09/16 11:19:47 kls Exp $
+ * $Id: receiver.c 4.2 2017/02/21 10:59:27 kls Exp $
  */
 
 #include "receiver.h"
@@ -38,8 +38,11 @@ bool cReceiver::AddPid(int Pid)
 {
   if (Pid) {
      if (numPids < MAXRECEIVEPIDS) {
-        if (!WantsPid(Pid))
+        if (!WantsPid(Pid)) {
            pids[numPids++] = Pid;
+           if (device)
+              device->AddPid(Pid);
+           }
         }
      else {
         dsyslog("too many PIDs in cReceiver (Pid = %d)", Pid);
@@ -87,6 +90,8 @@ void cReceiver::DelPid(int Pid)
             for ( ; i < numPids; i++) // we also copy the terminating 0!
                 pids[i] = pids[i + 1];
             numPids--;
+            if (device)
+               device->DelPid(Pid);
             return;
             }
          }
diff --git a/recording.c b/recording.c
index 5293459..6398058 100644
--- a/recording.c
+++ b/recording.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recording.c 4.6 2016/12/22 12:58:20 kls Exp $
+ * $Id: recording.c 4.7 2017/01/01 17:52:51 kls Exp $
  */
 
 #include "recording.h"
@@ -1914,16 +1914,20 @@ cRecordingsHandler::~cRecordingsHandler()
 void cRecordingsHandler::Action(void)
 {
   while (Running()) {
+        bool Sleep = false;
         {
           cMutexLock MutexLock(&mutex);
-          while (cRecordingsHandlerEntry *r = operations.First()) {
-                if (!r->Active(error))
-                   operations.Del(r);
-                }
-          if (!operations.Count())
+          if (cRecordingsHandlerEntry *r = operations.First()) {
+             if (!r->Active(error))
+                operations.Del(r);
+             else
+                Sleep = true;
+             }
+          else
              break;
         }
-        cCondWait::SleepMs(100);
+        if (Sleep)
+           cCondWait::SleepMs(100);
         }
 }
 
diff --git a/remux.c b/remux.c
index 2e1bc31..a2d2dd6 100644
--- a/remux.c
+++ b/remux.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remux.c 4.3 2016/12/22 12:58:20 kls Exp $
+ * $Id: remux.c 4.5 2017/03/26 13:07:01 kls Exp $
  */
 
 #include "remux.h"
@@ -144,6 +144,19 @@ void TsSetPcr(uchar *p, int64_t Pcr)
      }
 }
 
+int TsSync(const uchar *Data, int Length, const char *File, const char *Function, int Line)
+{
+  int Skipped = 0;
+  while (Length > 0 && (*Data != TS_SYNC_BYTE || Length > TS_SIZE && Data[TS_SIZE] != TS_SYNC_BYTE)) {
+        Data++;
+        Length--;
+        Skipped++;
+        }
+  if (Skipped && File && Function && Line)
+     esyslog("ERROR: skipped %d bytes to sync on start of TS packet at %s/%s(%d)", Skipped, File, Function, Line);
+  return Skipped;
+}
+
 int64_t TsGetPts(const uchar *p, int l)
 {
   // Find the first packet with a PTS and use it:
@@ -1557,13 +1570,8 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
   newFrame = independentFrame = false;
   while (Length >= MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE) { // makes sure we are looking at enough data, in case the frame type is not stored in the first TS packet
         // Sync on TS packet borders:
-        if (Data[0] != TS_SYNC_BYTE) {
-           int Skipped = 1;
-           while (Skipped < Length && (Data[Skipped] != TS_SYNC_BYTE || Length - Skipped > TS_SIZE && Data[Skipped + TS_SIZE] != TS_SYNC_BYTE))
-                 Skipped++;
-           esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
+        if (int Skipped = TS_SYNC(Data, Length))
            return Processed + Skipped;
-           }
         // Handle one TS packet:
         int Handled = TS_SIZE;
         if (TsHasPayload(Data) && !TsIsScrambled(Data)) {
@@ -1629,7 +1637,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
                           Div += parser->IFrameTemporalReferenceOffset();
                        if (Div <= 0)
                           Div = 1;
-                       uint32_t Delta = ptsValues[0] / Div;
+                       int Delta = ptsValues[0] / Div;
                        // determine frame info:
                        if (isVideo) {
                           if (Delta == 3753)
diff --git a/remux.h b/remux.h
index 5eab076..7dd5b16 100644
--- a/remux.h
+++ b/remux.h
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remux.h 4.1 2016/12/22 13:09:54 kls Exp $
+ * $Id: remux.h 4.3 2017/03/26 13:06:37 kls Exp $
  */
 
 #ifndef __REMUX_H
@@ -83,6 +83,12 @@ inline int TsPid(const uchar *p)
   return (p[1] & TS_PID_MASK_HI) * 256 + p[2];
 }
 
+inline void TsSetPid(uchar *p, int Pid)
+{
+  p[1] = (p[1] & ~TS_PID_MASK_HI) | ((Pid >> 8) & TS_PID_MASK_HI);
+  p[2] = Pid & 0x00FF;
+}
+
 inline bool TsIsScrambled(const uchar *p)
 {
   return p[3] & TS_SCRAMBLING_CONTROL;
@@ -138,6 +144,15 @@ inline int64_t TsGetPcr(const uchar *p)
 void TsHidePayload(uchar *p);
 void TsSetPcr(uchar *p, int64_t Pcr);
 
+// Helper macro and function to quickly check whether Data points to the beginning
+// of a TS packet. The return value is the number of bytes that need to be skipped
+// to synchronize on the next TS packet (zero if already sync'd). TsSync() can be
+// called directly, the macro just performs the initial check inline and adds some
+// debug information for logging.
+
+#define TS_SYNC(Data, Length) (*Data == TS_SYNC_BYTE ? 0 : TsSync(Data, Length, __FILE__, __FUNCTION__, __LINE__))
+int TsSync(const uchar *Data, int Length, const char *File = NULL, const char *Function = NULL, int Line = 0);
+
 // The following functions all take a pointer to a sequence of complete TS packets.
 
 int64_t TsGetPts(const uchar *p, int l);
diff --git a/ringbuffer.c b/ringbuffer.c
index d33a471..902c887 100644
--- a/ringbuffer.c
+++ b/ringbuffer.c
@@ -7,7 +7,7 @@
  * Parts of this file were inspired by the 'ringbuffy.c' from the
  * LinuxDVB driver (see linuxtv.org).
  *
- * $Id: ringbuffer.c 4.1 2016/12/22 10:26:13 kls Exp $
+ * $Id: ringbuffer.c 4.2 2017/03/19 12:43:36 kls Exp $
  */
 
 #include "ringbuffer.h"
@@ -216,9 +216,10 @@ int cRingBufferLinear::Available(void)
 
 void cRingBufferLinear::Clear(void)
 {
-  tail = head = margin;
+  int Head = head;
+  tail = Head;
 #ifdef DEBUGRINGBUFFERS
-  lastHead = head;
+  lastHead = Head;
   lastTail = tail;
   lastPut = lastGet = -1;
 #endif
diff --git a/ringbuffer.h b/ringbuffer.h
index 9699bbc..746fc51 100644
--- a/ringbuffer.h
+++ b/ringbuffer.h
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ringbuffer.h 4.1 2016/12/22 10:26:13 kls Exp $
+ * $Id: ringbuffer.h 4.2 2017/03/19 13:11:39 kls Exp $
  */
 
 #ifndef __RINGBUFFER_H
@@ -80,6 +80,8 @@ public:
   virtual int Free(void) { return Size() - Available() - 1 - margin; }
   virtual void Clear(void);
     ///< Immediately clears the ring buffer.
+    ///< This function may safely be called from the reading thread without additional
+    ///< locking. If called from the writing thread, proper locking must be used.
   int Read(int FileHandle, int Max = 0);
     ///< Reads at most Max bytes from FileHandle and stores them in the
     ///< ring buffer. If Max is 0, reads as many bytes as possible.
diff --git a/skinlcars.c b/skinlcars.c
index 0fae3b8..93ada5e 100644
--- a/skinlcars.c
+++ b/skinlcars.c
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinlcars.c 4.2 2016/12/22 14:05:56 kls Exp $
+ * $Id: skinlcars.c 4.3 2017/01/19 15:27:48 kls Exp $
  */
 
 // "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
@@ -254,7 +254,7 @@ static bool DrawDeviceData(cOsd *Osd, const cDevice *Device, int x0, int y0, int
      LastDeviceType = DeviceType;
      // CAM:
      if (CamSlot) {
-        cString s = cString::sprintf("CAM %d", CamSlot->SlotNumber());
+        cString s = cString::sprintf("CAM %d", CamSlot->MasterSlotNumber());
         Osd->DrawText(x, y1 - TinyFont->Height(), s, ColorFg, ColorBg, TinyFont);
         xs = max(xs, x + TinyFont->Width(s));
         }
diff --git a/sources.conf b/sources.conf
index 169eecf..735738d 100644
--- a/sources.conf
+++ b/sources.conf
@@ -182,6 +182,7 @@ S8W     Eutelsat 8 West A/C
 S7W     Nilesat 101/201 & Eutelsat 7 West A
 S5W     Eutelsat 5 West A
 S4W     Amos 2/3
+S3W     ABS-3A
 S1W     Thor 5/6
 S0.8W   Intelsat 10-02
 
diff --git a/tools.h b/tools.h
index 73cca5a..d2234c3 100644
--- a/tools.h
+++ b/tools.h
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.h 4.5 2016/12/23 13:56:35 kls Exp $
+ * $Id: tools.h 4.6 2017/03/16 16:04:43 kls Exp $
  */
 
 #ifndef __TOOLS_H
@@ -242,6 +242,17 @@ cString dtoa(double d, const char *Format = "%f");
     ///< the decimal point, independent of the currently selected locale.
     ///< If Format is given, it will be used instead of the default.
 cString itoa(int n);
+inline uint16_t Peek13(const uchar *p)
+{
+  uint16_t v = uint16_t(*p++ & 0x1F) << 8;
+  return v + (*p & 0xFF);
+}
+inline void Poke13(uchar *p, uint16_t v)
+{
+  v |= uint16_t(*p & ~0x1F) << 8;
+  *p++ = v >> 8;
+  *p = v & 0xFF;
+}
 cString AddDirectory(const char *DirName, const char *FileName);
 bool EntriesOnSameFileSystem(const char *File1, const char *File2);
     ///< Checks whether the given files are on the same file system. If either of the
@@ -744,6 +755,11 @@ public:
   }
   };
 
+inline int CompareInts(const void *a, const void *b)
+{
+  return *(const int *)a > *(const int *)b;
+}
+
 inline int CompareStrings(const void *a, const void *b)
 {
   return strcmp(*(const char **)a, *(const char **)b);
diff --git a/vdr.5 b/vdr.5
index 2a7b907..947bdc7 100644
--- a/vdr.5
+++ b/vdr.5
@@ -8,7 +8,7 @@
 .\" License as specified in the file COPYING that comes with the
 .\" vdr distribution.
 .\"
-.\" $Id: vdr.5 4.0 2015/02/17 13:43:53 kls Exp $
+.\" $Id: vdr.5 4.1 2017/01/09 13:35:08 kls Exp $
 .\"
 .TH vdr 5 "19 Feb 2015" "2.2" "Video Disk Recorder Files"
 .SH NAME
@@ -899,6 +899,20 @@ Note that the \fBevent id\fR that comes from the DVB data stream is actually
 just 16 bit wide. The internal representation in VDR allows for 32 bit to
 be used, so that external tools can generate EPG data that is guaranteed
 not to collide with the ids of existing data.
+.SS CAM DATA
+The file \fIcam.data\fR contains information about which CAM in the system can
+decrypt a particular channel.
+Each line in this file contains a channel id, followed by one or more (blank
+separated) numbers, indicating the CAMs that have successfully decrypted this
+channel earlier.
+
+When tuning to an encrypted channel, this information is used to select the
+proper CAM for decrypting this channel. This channel/CAM relationship is not
+hardcoded, though. If a given channel can't be decrypted with a CAM listed
+in this file, other CAMs will be tried just as well. The main purpose of this
+file is to speed up channel switching in systems with more than one CAM.
+
+This file will be read at program startup and saved when the program ends.
 .SS COMMANDLINE OPTIONS
 If started without any options, vdr tries to read any files in the directory
 /etc/vdr/conf.d with names that do not begin with a '.' and that end with '.conf'.
diff --git a/vdr.c b/vdr.c
index 8a49471..5b598f9 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,7 +22,7 @@
  *
  * The project's page is at http://www.tvdr.de
  *
- * $Id: vdr.c 4.9 2016/12/23 14:34:37 kls Exp $
+ * $Id: vdr.c 4.11 2017/03/25 14:20:30 kls Exp $
  */
 
 #include <getopt.h>
@@ -878,6 +878,10 @@ int main(int argc, char *argv[])
   if (!cPositioner::GetPositioner()) // no plugin has created a positioner
      new cDiseqcPositioner;
 
+  // CAM data:
+
+  ChannelCamRelations.Load(AddDirectory(CacheDirectory, "cam.data"));
+
   // Channel:
 
   if (!cDevice::WaitForAllDevicesReady(DEVICEREADYTIMEOUT))
@@ -1555,8 +1559,9 @@ Exit:
 
   StopSVDRPClientHandler();
   StopSVDRPServerHandler();
-  PluginManager.StopPlugins();
+  ChannelCamRelations.Save();
   cRecordControls::Shutdown();
+  PluginManager.StopPlugins();
   RecordingsHandler.DelAll();
   delete Menu;
   cControl::Shutdown();

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



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