mirror of
				https://github.com/vdr-projects/vdr.git
				synced 2025-03-01 10:50:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1895 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1895 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * device.c: The basic device interface
 | |
|  *
 | |
|  * See the main source file 'vdr.c' for copyright information and
 | |
|  * how to reach the author.
 | |
|  *
 | |
|  * $Id: device.c 4.8 2017/03/18 15:45:53 kls Exp $
 | |
|  */
 | |
| 
 | |
| #include "device.h"
 | |
| #include <errno.h>
 | |
| #include <math.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <sys/mman.h>
 | |
| #include "audio.h"
 | |
| #include "channels.h"
 | |
| #include "i18n.h"
 | |
| #include "player.h"
 | |
| #include "receiver.h"
 | |
| #include "status.h"
 | |
| #include "transfer.h"
 | |
| 
 | |
| // --- cLiveSubtitle ---------------------------------------------------------
 | |
| 
 | |
| class cLiveSubtitle : public cReceiver {
 | |
| protected:
 | |
|   virtual void Receive(const uchar *Data, int Length);
 | |
| public:
 | |
|   cLiveSubtitle(int SPid);
 | |
|   virtual ~cLiveSubtitle();
 | |
|   };
 | |
| 
 | |
| cLiveSubtitle::cLiveSubtitle(int SPid)
 | |
| {
 | |
|   AddPid(SPid);
 | |
| }
 | |
| 
 | |
| cLiveSubtitle::~cLiveSubtitle()
 | |
| {
 | |
|   cReceiver::Detach();
 | |
| }
 | |
| 
 | |
| void cLiveSubtitle::Receive(const uchar *Data, int Length)
 | |
| {
 | |
|   if (cDevice::PrimaryDevice())
 | |
|      cDevice::PrimaryDevice()->PlayTs(Data, Length);
 | |
| }
 | |
| 
 | |
| // --- cDeviceHook -----------------------------------------------------------
 | |
| 
 | |
| cDeviceHook::cDeviceHook(void)
 | |
| {
 | |
|   cDevice::deviceHooks.Add(this);
 | |
| }
 | |
| 
 | |
| bool cDeviceHook::DeviceProvidesTransponder(const cDevice *Device, const cChannel *Channel) const
 | |
| {
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // --- cDevice ---------------------------------------------------------------
 | |
| 
 | |
| // The minimum number of unknown PS1 packets to consider this a "pre 1.3.19 private stream":
 | |
| #define MIN_PRE_1_3_19_PRIVATESTREAM 10
 | |
| 
 | |
| int cDevice::numDevices = 0;
 | |
| int cDevice::useDevice = 0;
 | |
| int cDevice::nextCardIndex = 0;
 | |
| int cDevice::currentChannel = 1;
 | |
| cDevice *cDevice::device[MAXDEVICES] = { NULL };
 | |
| cDevice *cDevice::primaryDevice = NULL;
 | |
| cList<cDeviceHook> cDevice::deviceHooks;
 | |
| 
 | |
| cDevice::cDevice(void)
 | |
| :patPmtParser(true)
 | |
| {
 | |
|   cardIndex = nextCardIndex++;
 | |
|   dsyslog("new device number %d", CardIndex() + 1);
 | |
| 
 | |
|   SetDescription("device %d receiver", CardIndex() + 1);
 | |
| 
 | |
|   mute = false;
 | |
|   volume = Setup.CurrentVolume;
 | |
| 
 | |
|   sectionHandler = NULL;
 | |
|   eitFilter = NULL;
 | |
|   patFilter = NULL;
 | |
|   sdtFilter = NULL;
 | |
|   nitFilter = NULL;
 | |
| 
 | |
|   camSlot = NULL;
 | |
|   startScrambleDetection = 0;
 | |
|   scramblingTimeout = 0;
 | |
| 
 | |
|   occupiedTimeout = 0;
 | |
| 
 | |
|   player = NULL;
 | |
|   isPlayingVideo = false;
 | |
|   keepTracks = false; // used in ClrAvailableTracks()!
 | |
|   ClrAvailableTracks();
 | |
|   currentAudioTrack = ttNone;
 | |
|   currentAudioTrackMissingCount = 0;
 | |
|   currentSubtitleTrack = ttNone;
 | |
|   liveSubtitle = NULL;
 | |
|   dvbSubtitleConverter = NULL;
 | |
|   autoSelectPreferredSubtitleLanguage = true;
 | |
| 
 | |
|   for (int i = 0; i < MAXRECEIVERS; i++)
 | |
|       receiver[i] = NULL;
 | |
| 
 | |
|   if (numDevices < MAXDEVICES)
 | |
|      device[numDevices++] = this;
 | |
|   else
 | |
|      esyslog("ERROR: too many devices!");
 | |
| }
 | |
| 
 | |
| cDevice::~cDevice()
 | |
| {
 | |
|   Detach(player);
 | |
|   DetachAllReceivers();
 | |
|   delete liveSubtitle;
 | |
|   delete dvbSubtitleConverter;
 | |
|   if (this == primaryDevice)
 | |
|      primaryDevice = NULL;
 | |
| }
 | |
| 
 | |
| bool cDevice::WaitForAllDevicesReady(int Timeout)
 | |
| {
 | |
|   for (time_t t0 = time(NULL); time(NULL) - t0 < Timeout; ) {
 | |
|       bool ready = true;
 | |
|       for (int i = 0; i < numDevices; i++) {
 | |
|           if (device[i] && !device[i]->Ready()) {
 | |
|              ready = false;
 | |
|              cCondWait::SleepMs(100);
 | |
|              }
 | |
|           }
 | |
|       if (ready)
 | |
|          return true;
 | |
|       }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void cDevice::SetUseDevice(int n)
 | |
| {
 | |
|   if (n < MAXDEVICES)
 | |
|      useDevice |= (1 << n);
 | |
| }
 | |
| 
 | |
| int cDevice::NextCardIndex(int n)
 | |
| {
 | |
|   if (n > 0) {
 | |
|      nextCardIndex += n;
 | |
|      if (nextCardIndex >= MAXDEVICES)
 | |
|         esyslog("ERROR: nextCardIndex too big (%d)", nextCardIndex);
 | |
|      }
 | |
|   else if (n < 0)
 | |
|      esyslog("ERROR: invalid value in nextCardIndex(%d)", n);
 | |
|   return nextCardIndex;
 | |
| }
 | |
| 
 | |
| int cDevice::DeviceNumber(void) const
 | |
| {
 | |
|   for (int i = 0; i < numDevices; i++) {
 | |
|       if (device[i] == this)
 | |
|          return i;
 | |
|       }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| cString cDevice::DeviceType(void) const
 | |
| {
 | |
|   return "";
 | |
| }
 | |
| 
 | |
| cString cDevice::DeviceName(void) const
 | |
| {
 | |
|   return "";
 | |
| }
 | |
| 
 | |
| void cDevice::MakePrimaryDevice(bool On)
 | |
| {
 | |
|   if (!On) {
 | |
|      DELETENULL(liveSubtitle);
 | |
|      DELETENULL(dvbSubtitleConverter);
 | |
|      }
 | |
| }
 | |
| 
 | |
| bool cDevice::SetPrimaryDevice(int n)
 | |
| {
 | |
|   n--;
 | |
|   if (0 <= n && n < numDevices && device[n]) {
 | |
|      isyslog("setting primary device to %d", n + 1);
 | |
|      if (primaryDevice)
 | |
|         primaryDevice->MakePrimaryDevice(false);
 | |
|      primaryDevice = device[n];
 | |
|      primaryDevice->MakePrimaryDevice(true);
 | |
|      primaryDevice->SetVideoFormat(Setup.VideoFormat);
 | |
|      primaryDevice->SetVolumeDevice(Setup.CurrentVolume);
 | |
|      return true;
 | |
|      }
 | |
|   esyslog("ERROR: invalid primary device number: %d", n + 1);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::HasDecoder(void) const
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| cSpuDecoder *cDevice::GetSpuDecoder(void)
 | |
| {
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| cDevice *cDevice::ActualDevice(void)
 | |
| {
 | |
|   cDevice *d = cTransferControl::ReceiverDevice();
 | |
|   if (!d)
 | |
|      d = PrimaryDevice();
 | |
|   return d;
 | |
| }
 | |
| 
 | |
| cDevice *cDevice::GetDevice(int Index)
 | |
| {
 | |
|   return (0 <= Index && Index < numDevices) ? device[Index] : NULL;
 | |
| }
 | |
| 
 | |
| static int GetClippedNumProvidedSystems(int AvailableBits, cDevice *Device)
 | |
| {
 | |
|   int MaxNumProvidedSystems = (1 << AvailableBits) - 1;
 | |
|   int NumProvidedSystems = Device->NumProvidedSystems();
 | |
|   if (NumProvidedSystems > MaxNumProvidedSystems) {
 | |
|      esyslog("ERROR: device %d supports %d modulation systems but cDevice::GetDevice() currently only supports %d delivery systems which should be fixed", Device->CardIndex() + 1, NumProvidedSystems, MaxNumProvidedSystems);
 | |
|      NumProvidedSystems = MaxNumProvidedSystems;
 | |
|      }
 | |
|   else if (NumProvidedSystems <= 0) {
 | |
|      esyslog("ERROR: device %d reported an invalid number (%d) of supported delivery systems - assuming 1", Device->CardIndex() + 1, NumProvidedSystems);
 | |
|      NumProvidedSystems = 1;
 | |
|      }
 | |
|   return NumProvidedSystems;
 | |
| }
 | |
| 
 | |
| cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView, bool Query)
 | |
| {
 | |
|   // Collect the current priorities of all CAM slots that can decrypt the channel:
 | |
|   int NumCamSlots = CamSlots.Count();
 | |
|   int SlotPriority[NumCamSlots];
 | |
|   int NumUsableSlots = 0;
 | |
|   bool InternalCamNeeded = false;
 | |
|   if (Channel->Ca() >= CA_ENCRYPTED_MIN) {
 | |
|      for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
 | |
|          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->MasterSlotNumber())) {
 | |
|                   SlotPriority[CamSlot->Index()] = CamSlot->Priority();
 | |
|                   NumUsableSlots++;
 | |
|                   }
 | |
|                }
 | |
|             }
 | |
|          }
 | |
|      if (!NumUsableSlots)
 | |
|         InternalCamNeeded = true; // no CAM is able to decrypt this channel
 | |
|      }
 | |
| 
 | |
|   bool NeedsDetachReceivers = false;
 | |
|   cDevice *d = NULL;
 | |
|   cCamSlot *s = NULL;
 | |
| 
 | |
|   uint32_t Impact = 0xFFFFFFFF; // we're looking for a device with the least impact
 | |
|   for (int j = 0; j < NumCamSlots || !NumUsableSlots; j++) {
 | |
|       if (NumUsableSlots && SlotPriority[j] > MAXPRIORITY)
 | |
|          continue; // there is no CAM available in this slot
 | |
|       for (int i = 0; i < numDevices; i++) {
 | |
|           if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device[i]->CardIndex() + 1)
 | |
|              continue; // a specific card was requested, but not this one
 | |
|           bool HasInternalCam = device[i]->HasInternalCam();
 | |
|           if (InternalCamNeeded && !HasInternalCam)
 | |
|              continue; // no CAM is able to decrypt this channel and the device uses vdr handled CAMs
 | |
|           if (NumUsableSlots && !HasInternalCam && !CamSlots.Get(j)->Assign(device[i], true))
 | |
|              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) {
 | |
|                 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
 | |
|              // a numeric value). The sequence in which the conditions are listed corresponds
 | |
|              // to their individual severity, where the one listed first will make the most
 | |
|              // difference, because it results in the most significant bit of the result.
 | |
|              uint32_t imp = 0;
 | |
|              imp <<= 1; imp |= LiveView ? !device[i]->IsPrimaryDevice() || ndr : 0;                                  // prefer the primary device for live viewing if we don't need to detach existing receivers
 | |
|              imp <<= 1; imp |= !device[i]->Receiving() && (device[i] != cTransferControl::ReceiverDevice() || device[i]->IsPrimaryDevice()) || ndr; // use receiving devices if we don't need to detach existing receivers, but avoid primary device in local transfer mode
 | |
|              imp <<= 1; imp |= device[i]->Receiving();                                                               // avoid devices that are receiving
 | |
|              imp <<= 4; imp |= GetClippedNumProvidedSystems(4, device[i]) - 1;                                       // avoid cards which support multiple delivery systems
 | |
|              imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice();                                      // avoid the Transfer Mode receiver device
 | |
|              imp <<= 8; imp |= device[i]->Priority() - IDLEPRIORITY;                                                 // use the device with the lowest priority (- IDLEPRIORITY to assure that values -100..99 can be used)
 | |
|              imp <<= 8; imp |= ((NumUsableSlots && !HasInternalCam) ? SlotPriority[j] : IDLEPRIORITY) - IDLEPRIORITY;// use the CAM slot with the lowest priority (- IDLEPRIORITY to assure that values -100..99 can be used)
 | |
|              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(), 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.
 | |
|                 Impact = imp;
 | |
|                 d = device[i];
 | |
|                 NeedsDetachReceivers = ndr;
 | |
|                 if (NumUsableSlots && !HasInternalCam)
 | |
|                    s = CamSlots.Get(j);
 | |
|                 }
 | |
|              }
 | |
|           }
 | |
|       if (!NumUsableSlots)
 | |
|          break; // no CAM necessary, so just one loop over the devices
 | |
|       }
 | |
|   if (d && !Query) {
 | |
|      if (NeedsDetachReceivers)
 | |
|         d->DetachAllReceivers();
 | |
|      if (s) {
 | |
|         // 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
 | |
|                  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()) {
 | |
|                        s->Device()->DetachAllReceivers();
 | |
|                        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
 | |
|                     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
 | |
|               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()) {
 | |
|                     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
 | |
|                  s->Assign(d);
 | |
|                  }
 | |
|               }
 | |
|            }
 | |
|         }
 | |
|      else if (d->CamSlot() && !d->CamSlot()->IsDecrypting())
 | |
|         d->CamSlot()->Assign(NULL);
 | |
|      }
 | |
|   return d;
 | |
| }
 | |
| 
 | |
| cDevice *cDevice::GetDeviceForTransponder(const cChannel *Channel, int Priority)
 | |
| {
 | |
|   cDevice *Device = NULL;
 | |
|   for (int i = 0; i < cDevice::NumDevices(); i++) {
 | |
|       if (cDevice *d = cDevice::GetDevice(i)) {
 | |
|          if (d->IsTunedToTransponder(Channel))
 | |
|             return d; // if any device is tuned to the transponder, we're done
 | |
|          if (d->ProvidesTransponder(Channel)) {
 | |
|             if (d->MaySwitchTransponder(Channel))
 | |
|                Device = d; // this device may switch to the transponder without disturbing any receiver or live view
 | |
|             else if (!d->Occupied() && d->MaySwitchTransponder(Channel)) { // MaySwitchTransponder() implicitly calls Occupied()
 | |
|                if (d->Priority() < Priority && (!Device || d->Priority() < Device->Priority()))
 | |
|                   Device = d; // use this one only if no other with less impact can be found
 | |
|                }
 | |
|             }
 | |
|          }
 | |
|       }
 | |
|   return Device;
 | |
| }
 | |
| 
 | |
| bool cDevice::HasCi(void)
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void cDevice::SetCamSlot(cCamSlot *CamSlot)
 | |
| {
 | |
|   LOCK_THREAD;
 | |
|   camSlot = CamSlot;
 | |
| }
 | |
| 
 | |
| void cDevice::Shutdown(void)
 | |
| {
 | |
|   deviceHooks.Clear();
 | |
|   for (int i = 0; i < numDevices; i++) {
 | |
|       delete device[i];
 | |
|       device[i] = NULL;
 | |
|       }
 | |
| }
 | |
| 
 | |
| uchar *cDevice::GrabImage(int &Size, bool Jpeg, int Quality, int SizeX, int SizeY)
 | |
| {
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| bool cDevice::GrabImageFile(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY)
 | |
| {
 | |
|   int result = 0;
 | |
|   int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
 | |
|   if (fd >= 0) {
 | |
|      int ImageSize;
 | |
|      uchar *Image = GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
 | |
|      if (Image) {
 | |
|         if (safe_write(fd, Image, ImageSize) == ImageSize)
 | |
|            isyslog("grabbed image to %s", FileName);
 | |
|         else {
 | |
|            LOG_ERROR_STR(FileName);
 | |
|            result |= 1;
 | |
|            }
 | |
|         free(Image);
 | |
|         }
 | |
|      else
 | |
|         result |= 1;
 | |
|      close(fd);
 | |
|      }
 | |
|   else {
 | |
|      LOG_ERROR_STR(FileName);
 | |
|      result |= 1;
 | |
|      }
 | |
|   return result == 0;
 | |
| }
 | |
| 
 | |
| void cDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
 | |
| {
 | |
|   cSpuDecoder *spuDecoder = GetSpuDecoder();
 | |
|   if (spuDecoder) {
 | |
|      if (Setup.VideoFormat)
 | |
|         spuDecoder->setScaleMode(cSpuDecoder::eSpuNormal);
 | |
|      else {
 | |
|         switch (VideoDisplayFormat) {
 | |
|                case vdfPanAndScan:
 | |
|                     spuDecoder->setScaleMode(cSpuDecoder::eSpuPanAndScan);
 | |
|                     break;
 | |
|                case vdfLetterBox:
 | |
|                     spuDecoder->setScaleMode(cSpuDecoder::eSpuLetterBox);
 | |
|                     break;
 | |
|                case vdfCenterCutOut:
 | |
|                     spuDecoder->setScaleMode(cSpuDecoder::eSpuNormal);
 | |
|                     break;
 | |
|                default: esyslog("ERROR: invalid value for VideoDisplayFormat '%d'", VideoDisplayFormat);
 | |
|                }
 | |
|         }
 | |
|      }
 | |
| }
 | |
| 
 | |
| void cDevice::SetVideoFormat(bool VideoFormat16_9)
 | |
| {
 | |
| }
 | |
| 
 | |
| void cDevice::GetVideoSize(int &Width, int &Height, double &VideoAspect)
 | |
| {
 | |
|   Width = 0;
 | |
|   Height = 0;
 | |
|   VideoAspect = 1.0;
 | |
| }
 | |
| 
 | |
| void cDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect)
 | |
| {
 | |
|   Width = 720;
 | |
|   Height = 480;
 | |
|   PixelAspect = 1.0;
 | |
| }
 | |
| 
 | |
| //#define PRINTPIDS(s) { char b[500]; char *q = b; q += sprintf(q, "%d %s ", CardIndex(), s); for (int i = 0; i < MAXPIDHANDLES; i++) q += sprintf(q, " %s%4d %d", i == ptOther ? "* " : "", pidHandles[i].pid, pidHandles[i].used); dsyslog("%s", b); }
 | |
| #define PRINTPIDS(s)
 | |
| 
 | |
| bool cDevice::HasPid(int Pid) const
 | |
| {
 | |
|   cMutexLock MutexLock(&mutexPids);
 | |
|   for (int i = 0; i < MAXPIDHANDLES; i++) {
 | |
|       if (pidHandles[i].pid == Pid)
 | |
|          return true;
 | |
|       }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::AddPid(int Pid, ePidType PidType, int StreamType)
 | |
| {
 | |
|   cMutexLock MutexLock(&mutexPids);
 | |
|   if (Pid || PidType == ptPcr) {
 | |
|      int n = -1;
 | |
|      int a = -1;
 | |
|      if (PidType != ptPcr) { // PPID always has to be explicit
 | |
|         for (int i = 0; i < MAXPIDHANDLES; i++) {
 | |
|             if (i != ptPcr) {
 | |
|                if (pidHandles[i].pid == Pid)
 | |
|                   n = i;
 | |
|                else if (a < 0 && i >= ptOther && !pidHandles[i].used)
 | |
|                   a = i;
 | |
|                }
 | |
|             }
 | |
|         }
 | |
|      if (n >= 0) {
 | |
|         // The Pid is already in use
 | |
|         if (++pidHandles[n].used == 2 && n <= ptTeletext) {
 | |
|            // It's a special PID that may have to be switched into "tap" mode
 | |
|            PRINTPIDS("A");
 | |
|            if (!SetPid(&pidHandles[n], n, true)) {
 | |
|               esyslog("ERROR: can't set PID %d on device %d", Pid, CardIndex() + 1);
 | |
|               if (PidType <= ptTeletext)
 | |
|                  DetachAll(Pid);
 | |
|               DelPid(Pid, PidType);
 | |
|               return false;
 | |
|               }
 | |
|            if (camSlot)
 | |
|               camSlot->SetPid(Pid, true);
 | |
|            }
 | |
|         PRINTPIDS("a");
 | |
|         return true;
 | |
|         }
 | |
|      else if (PidType < ptOther) {
 | |
|         // The Pid is not yet in use and it is a special one
 | |
|         n = PidType;
 | |
|         }
 | |
|      else if (a >= 0) {
 | |
|         // The Pid is not yet in use and we have a free slot
 | |
|         n = a;
 | |
|         }
 | |
|      else {
 | |
|         esyslog("ERROR: no free slot for PID %d on device %d", Pid, CardIndex() + 1);
 | |
|         return false;
 | |
|         }
 | |
|      if (n >= 0) {
 | |
|         pidHandles[n].pid = Pid;
 | |
|         pidHandles[n].streamType = StreamType;
 | |
|         pidHandles[n].used = 1;
 | |
|         PRINTPIDS("C");
 | |
|         if (!SetPid(&pidHandles[n], n, true)) {
 | |
|            esyslog("ERROR: can't set PID %d on device %d", Pid, CardIndex() + 1);
 | |
|            if (PidType <= ptTeletext)
 | |
|               DetachAll(Pid);
 | |
|            DelPid(Pid, PidType);
 | |
|            return false;
 | |
|            }
 | |
|         if (camSlot)
 | |
|            camSlot->SetPid(Pid, true);
 | |
|         }
 | |
|      }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void cDevice::DelPid(int Pid, ePidType PidType)
 | |
| {
 | |
|   cMutexLock MutexLock(&mutexPids);
 | |
|   if (Pid || PidType == ptPcr) {
 | |
|      int n = -1;
 | |
|      if (PidType == ptPcr)
 | |
|         n = PidType; // PPID always has to be explicit
 | |
|      else {
 | |
|         for (int i = 0; i < MAXPIDHANDLES; i++) {
 | |
|             if (pidHandles[i].pid == Pid) {
 | |
|                n = i;
 | |
|                break;
 | |
|                }
 | |
|             }
 | |
|         }
 | |
|      if (n >= 0 && pidHandles[n].used) {
 | |
|         PRINTPIDS("D");
 | |
|         if (--pidHandles[n].used < 2) {
 | |
|            SetPid(&pidHandles[n], n, false);
 | |
|            if (pidHandles[n].used == 0) {
 | |
|               pidHandles[n].handle = -1;
 | |
|               pidHandles[n].pid = 0;
 | |
|               if (camSlot)
 | |
|                  camSlot->SetPid(Pid, false);
 | |
|               }
 | |
|            }
 | |
|         PRINTPIDS("E");
 | |
|         }
 | |
|      }
 | |
| }
 | |
| 
 | |
| bool cDevice::SetPid(cPidHandle *Handle, int Type, bool On)
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void cDevice::DelLivePids(void)
 | |
| {
 | |
|   cMutexLock MutexLock(&mutexPids);
 | |
|   for (int i = ptAudio; i < ptOther; i++) {
 | |
|       if (pidHandles[i].pid)
 | |
|          DelPid(pidHandles[i].pid, ePidType(i));
 | |
|       }
 | |
| }
 | |
| 
 | |
| void cDevice::StartSectionHandler(void)
 | |
| {
 | |
|   if (!sectionHandler) {
 | |
|      sectionHandler = new cSectionHandler(this);
 | |
|      AttachFilter(eitFilter = new cEitFilter);
 | |
|      AttachFilter(patFilter = new cPatFilter);
 | |
|      AttachFilter(sdtFilter = new cSdtFilter(patFilter));
 | |
|      AttachFilter(nitFilter = new cNitFilter(sdtFilter));
 | |
|      }
 | |
| }
 | |
| 
 | |
| void cDevice::StopSectionHandler(void)
 | |
| {
 | |
|   if (sectionHandler) {
 | |
|      delete nitFilter;
 | |
|      delete sdtFilter;
 | |
|      delete patFilter;
 | |
|      delete eitFilter;
 | |
|      delete sectionHandler;
 | |
|      nitFilter = NULL;
 | |
|      sdtFilter = NULL;
 | |
|      patFilter = NULL;
 | |
|      eitFilter = NULL;
 | |
|      sectionHandler = NULL;
 | |
|      }
 | |
| }
 | |
| 
 | |
| int cDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
 | |
| {
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| int cDevice::ReadFilter(int Handle, void *Buffer, size_t Length)
 | |
| {
 | |
|   return safe_read(Handle, Buffer, Length);
 | |
| }
 | |
| 
 | |
| void cDevice::CloseFilter(int Handle)
 | |
| {
 | |
|   close(Handle);
 | |
| }
 | |
| 
 | |
| void cDevice::AttachFilter(cFilter *Filter)
 | |
| {
 | |
|   if (sectionHandler)
 | |
|      sectionHandler->Attach(Filter);
 | |
| }
 | |
| 
 | |
| void cDevice::Detach(cFilter *Filter)
 | |
| {
 | |
|   if (sectionHandler)
 | |
|      sectionHandler->Detach(Filter);
 | |
| }
 | |
| 
 | |
| bool cDevice::ProvidesSource(int Source) const
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::DeviceHooksProvidesTransponder(const cChannel *Channel) const
 | |
| {
 | |
|   cDeviceHook *Hook = deviceHooks.First();
 | |
|   while (Hook) {
 | |
|         if (!Hook->DeviceProvidesTransponder(this, Channel))
 | |
|            return false;
 | |
|         Hook = deviceHooks.Next(Hook);
 | |
|         }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool cDevice::ProvidesTransponder(const cChannel *Channel) const
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::ProvidesTransponderExclusively(const cChannel *Channel) const
 | |
| {
 | |
|   for (int i = 0; i < numDevices; i++) {
 | |
|       if (device[i] && device[i] != this && device[i]->ProvidesTransponder(Channel))
 | |
|          return false;
 | |
|       }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool cDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::ProvidesEIT(void) const
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| int cDevice::NumProvidedSystems(void) const
 | |
| {
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| const cPositioner *cDevice::Positioner(void) const
 | |
| {
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| int cDevice::SignalStrength(void) const
 | |
| {
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| int cDevice::SignalQuality(void) const
 | |
| {
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| const cChannel *cDevice::GetCurrentlyTunedTransponder(void) const
 | |
| {
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| bool cDevice::IsTunedToTransponder(const cChannel *Channel) const
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::MaySwitchTransponder(const cChannel *Channel) const
 | |
| {
 | |
|   return time(NULL) > occupiedTimeout && !Receiving() && !(pidHandles[ptAudio].pid || pidHandles[ptVideo].pid || pidHandles[ptDolby].pid);
 | |
| }
 | |
| 
 | |
| bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView)
 | |
| {
 | |
|   if (LiveView) {
 | |
|      isyslog("switching to channel %d (%s)", Channel->Number(), Channel->Name());
 | |
|      cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
 | |
|      }
 | |
|   for (int i = 3; i--;) {
 | |
|       switch (SetChannel(Channel, LiveView)) {
 | |
|         case scrOk:           return true;
 | |
|         case scrNotAvailable: Skins.Message(mtInfo, tr("Channel not available!"));
 | |
|                               return false;
 | |
|         case scrNoTransfer:   Skins.Message(mtError, tr("Can't start Transfer Mode!"));
 | |
|                               return false;
 | |
|         case scrFailed:       break; // loop will retry
 | |
|         default:              esyslog("ERROR: invalid return value from SetChannel");
 | |
|         }
 | |
|       esyslog("retrying");
 | |
|       }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::SwitchChannel(int Direction)
 | |
| {
 | |
|   bool result = false;
 | |
|   Direction = sgn(Direction);
 | |
|   if (Direction) {
 | |
|      cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
 | |
|      int n = CurrentChannel() + Direction;
 | |
|      int first = n;
 | |
|      LOCK_CHANNELS_READ;
 | |
|      const cChannel *Channel;
 | |
|      while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) {
 | |
|            // try only channels which are currently available
 | |
|            if (GetDevice(Channel, LIVEPRIORITY, true, true))
 | |
|               break;
 | |
|            n = Channel->Number() + Direction;
 | |
|            }
 | |
|      if (Channel) {
 | |
|         int d = n - first;
 | |
|         if (abs(d) == 1)
 | |
|            dsyslog("skipped channel %d", first);
 | |
|         else if (d)
 | |
|            dsyslog("skipped channels %d..%d", first, n - sgn(d));
 | |
|         if (PrimaryDevice()->SwitchChannel(Channel, true))
 | |
|            result = true;
 | |
|         }
 | |
|      else if (n != first)
 | |
|         Skins.Message(mtError, tr("Channel not available!"));
 | |
|      }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
 | |
| {
 | |
|   cStatus::MsgChannelSwitch(this, 0, LiveView);
 | |
| 
 | |
|   if (LiveView) {
 | |
|      StopReplay();
 | |
|      DELETENULL(liveSubtitle);
 | |
|      DELETENULL(dvbSubtitleConverter);
 | |
|      }
 | |
| 
 | |
|   cDevice *Device = (LiveView && IsPrimaryDevice()) ? GetDevice(Channel, LIVEPRIORITY, true) : this;
 | |
| 
 | |
|   bool NeedsTransferMode = LiveView && Device != PrimaryDevice();
 | |
|   // If the CAM slot wants the TS data, we need to switch to Transfer Mode:
 | |
|   if (!NeedsTransferMode && LiveView && IsPrimaryDevice() && CamSlot() && CamSlot()->WantsTsData())
 | |
|      NeedsTransferMode = true;
 | |
| 
 | |
|   eSetChannelResult Result = scrOk;
 | |
| 
 | |
|   // If this DVB card can't receive this channel, let's see if we can
 | |
|   // use the card that actually can receive it and transfer data from there:
 | |
| 
 | |
|   if (NeedsTransferMode) {
 | |
|      if (Device && PrimaryDevice()->CanReplay()) {
 | |
|         if (Device->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()!
 | |
|            cControl::Launch(new cTransferControl(Device, Channel));
 | |
|         else
 | |
|            Result = scrNoTransfer;
 | |
|         }
 | |
|      else
 | |
|         Result = scrNotAvailable;
 | |
|      }
 | |
|   else {
 | |
|      // Stop section handling:
 | |
|      if (sectionHandler) {
 | |
|         sectionHandler->SetStatus(false);
 | |
|         sectionHandler->SetChannel(NULL);
 | |
|         }
 | |
|      // Tell the camSlot about the channel switch and add all PIDs of this
 | |
|      // channel to it, for possible later decryption:
 | |
|      if (camSlot)
 | |
|         camSlot->AddChannel(Channel);
 | |
|      if (SetChannelDevice(Channel, LiveView)) {
 | |
|         // Start section handling:
 | |
|         if (sectionHandler) {
 | |
|            if (patFilter)
 | |
|               patFilter->Trigger(Channel->Sid());
 | |
|            sectionHandler->SetChannel(Channel);
 | |
|            sectionHandler->SetStatus(true);
 | |
|            }
 | |
|         // Start decrypting any PIDs that might have been set in SetChannelDevice():
 | |
|         if (camSlot)
 | |
|            camSlot->StartDecrypting();
 | |
|         }
 | |
|      else
 | |
|         Result = scrFailed;
 | |
|      }
 | |
| 
 | |
|   if (Result == scrOk) {
 | |
|      if (LiveView && IsPrimaryDevice()) {
 | |
|         currentChannel = Channel->Number();
 | |
|         // Set the available audio tracks:
 | |
|         ClrAvailableTracks();
 | |
|         for (int i = 0; i < MAXAPIDS; i++)
 | |
|             SetAvailableTrack(ttAudio, i, Channel->Apid(i), Channel->Alang(i));
 | |
|         if (Setup.UseDolbyDigital) {
 | |
|            for (int i = 0; i < MAXDPIDS; i++)
 | |
|                SetAvailableTrack(ttDolby, i, Channel->Dpid(i), Channel->Dlang(i));
 | |
|            }
 | |
|         for (int i = 0; i < MAXSPIDS; i++)
 | |
|             SetAvailableTrack(ttSubtitle, i, Channel->Spid(i), Channel->Slang(i));
 | |
|         if (!NeedsTransferMode)
 | |
|            EnsureAudioTrack(true);
 | |
|         EnsureSubtitleTrack();
 | |
|         }
 | |
|      cStatus::MsgChannelSwitch(this, Channel->Number(), LiveView); // only report status if channel switch successful
 | |
|      }
 | |
| 
 | |
|   return Result;
 | |
| }
 | |
| 
 | |
| void cDevice::ForceTransferMode(void)
 | |
| {
 | |
|   if (!cTransferControl::ReceiverDevice()) {
 | |
|      LOCK_CHANNELS_READ;
 | |
|      if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel()))
 | |
|         SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
 | |
|      }
 | |
| }
 | |
| 
 | |
| int cDevice::Occupied(void) const
 | |
| {
 | |
|   int Seconds = occupiedTimeout - time(NULL);
 | |
|   return Seconds > 0 ? Seconds : 0;
 | |
| }
 | |
| 
 | |
| void cDevice::SetOccupied(int Seconds)
 | |
| {
 | |
|   if (Seconds >= 0)
 | |
|      occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
 | |
| }
 | |
| 
 | |
| bool cDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::HasLock(int TimeoutMs) const
 | |
| {
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool cDevice::HasProgramme(void) const
 | |
| {
 | |
|   return Replaying() || pidHandles[ptAudio].pid || pidHandles[ptVideo].pid;
 | |
| }
 | |
| 
 | |
| int cDevice::GetAudioChannelDevice(void)
 | |
| {
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| void cDevice::SetAudioChannelDevice(int AudioChannel)
 | |
| {
 | |
| }
 | |
| 
 | |
| void cDevice::SetVolumeDevice(int Volume)
 | |
| {
 | |
| }
 | |
| 
 | |
| void cDevice::SetDigitalAudioDevice(bool On)
 | |
| {
 | |
| }
 | |
| 
 | |
| void cDevice::SetAudioTrackDevice(eTrackType Type)
 | |
| {
 | |
| }
 | |
| 
 | |
| void cDevice::SetSubtitleTrackDevice(eTrackType Type)
 | |
| {
 | |
| }
 | |
| 
 | |
| bool cDevice::ToggleMute(void)
 | |
| {
 | |
|   int OldVolume = volume;
 | |
|   mute = !mute;
 | |
|   //XXX why is it necessary to use different sequences???
 | |
|   if (mute) {
 | |
|      SetVolume(0, true);
 | |
|      Audios.MuteAudio(mute); // Mute external audio after analog audio
 | |
|      }
 | |
|   else {
 | |
|      Audios.MuteAudio(mute); // Enable external audio before analog audio
 | |
|      SetVolume(OldVolume, true);
 | |
|      }
 | |
|   volume = OldVolume;
 | |
|   return mute;
 | |
| }
 | |
| 
 | |
| int cDevice::GetAudioChannel(void)
 | |
| {
 | |
|   int c = GetAudioChannelDevice();
 | |
|   return (0 <= c && c <= 2) ? c : 0;
 | |
| }
 | |
| 
 | |
| void cDevice::SetAudioChannel(int AudioChannel)
 | |
| {
 | |
|   if (0 <= AudioChannel && AudioChannel <= 2)
 | |
|      SetAudioChannelDevice(AudioChannel);
 | |
| }
 | |
| 
 | |
| void cDevice::SetVolume(int Volume, bool Absolute)
 | |
| {
 | |
|   int OldVolume = volume;
 | |
|   double VolumeDelta = double(MAXVOLUME) / Setup.VolumeSteps;
 | |
|   double VolumeLinearize = (Setup.VolumeLinearize >= 0) ? (Setup.VolumeLinearize / 10.0 + 1.0) : (1.0 / ((-Setup.VolumeLinearize / 10.0) + 1.0));
 | |
|   volume = constrain(int(floor((Absolute ? Volume : volume + Volume) / VolumeDelta + 0.5) * VolumeDelta), 0, MAXVOLUME);
 | |
|   SetVolumeDevice(MAXVOLUME - int(pow(1.0 - pow(double(volume) / MAXVOLUME, VolumeLinearize), 1.0 / VolumeLinearize) * MAXVOLUME));
 | |
|   Absolute |= mute;
 | |
|   cStatus::MsgSetVolume(Absolute ? volume : volume - OldVolume, Absolute);
 | |
|   if (volume > 0) {
 | |
|      mute = false;
 | |
|      Audios.MuteAudio(mute);
 | |
|      }
 | |
| }
 | |
| 
 | |
| void cDevice::ClrAvailableTracks(bool DescriptionsOnly, bool IdsOnly)
 | |
| {
 | |
|   if (keepTracks)
 | |
|      return;
 | |
|   if (DescriptionsOnly) {
 | |
|      for (int i = ttNone; i < ttMaxTrackTypes; i++)
 | |
|          *availableTracks[i].description = 0;
 | |
|      }
 | |
|   else {
 | |
|      if (IdsOnly) {
 | |
|         for (int i = ttNone; i < ttMaxTrackTypes; i++)
 | |
|             availableTracks[i].id = 0;
 | |
|         }
 | |
|      else
 | |
|         memset(availableTracks, 0, sizeof(availableTracks));
 | |
|      pre_1_3_19_PrivateStream = 0;
 | |
|      SetAudioChannel(0); // fall back to stereo
 | |
|      currentAudioTrackMissingCount = 0;
 | |
|      currentAudioTrack = ttNone;
 | |
|      currentSubtitleTrack = ttNone;
 | |
|      }
 | |
| }
 | |
| 
 | |
| bool cDevice::SetAvailableTrack(eTrackType Type, int Index, uint16_t Id, const char *Language, const char *Description)
 | |
| {
 | |
|   eTrackType t = eTrackType(Type + Index);
 | |
|   if (Type == ttAudio && IS_AUDIO_TRACK(t) ||
 | |
|       Type == ttDolby && IS_DOLBY_TRACK(t) ||
 | |
|       Type == ttSubtitle && IS_SUBTITLE_TRACK(t)) {
 | |
|      if (Language)
 | |
|         strn0cpy(availableTracks[t].language, Language, sizeof(availableTracks[t].language));
 | |
|      if (Description)
 | |
|         Utf8Strn0Cpy(availableTracks[t].description, Description, sizeof(availableTracks[t].description));
 | |
|      if (Id) {
 | |
|         availableTracks[t].id = Id; // setting 'id' last to avoid the need for extensive locking
 | |
|         if (Type == ttAudio || Type == ttDolby) {
 | |
|            int numAudioTracks = NumAudioTracks();
 | |
|            if (!availableTracks[currentAudioTrack].id && numAudioTracks && currentAudioTrackMissingCount++ > numAudioTracks * 10)
 | |
|               EnsureAudioTrack();
 | |
|            else if (t == currentAudioTrack)
 | |
|               currentAudioTrackMissingCount = 0;
 | |
|            }
 | |
|         else if (Type == ttSubtitle && autoSelectPreferredSubtitleLanguage)
 | |
|            EnsureSubtitleTrack();
 | |
|         }
 | |
|      return true;
 | |
|      }
 | |
|   else
 | |
|      esyslog("ERROR: SetAvailableTrack called with invalid Type/Index (%d/%d)", Type, Index);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| const tTrackId *cDevice::GetTrack(eTrackType Type)
 | |
| {
 | |
|   return (ttNone < Type && Type < ttMaxTrackTypes) ? &availableTracks[Type] : NULL;
 | |
| }
 | |
| 
 | |
| int cDevice::NumTracks(eTrackType FirstTrack, eTrackType LastTrack) const
 | |
| {
 | |
|   int n = 0;
 | |
|   for (int i = FirstTrack; i <= LastTrack; i++) {
 | |
|       if (availableTracks[i].id)
 | |
|          n++;
 | |
|       }
 | |
|   return n;
 | |
| }
 | |
| 
 | |
| int cDevice::NumAudioTracks(void) const
 | |
| {
 | |
|   return NumTracks(ttAudioFirst, ttDolbyLast);
 | |
| }
 | |
| 
 | |
| int cDevice::NumSubtitleTracks(void) const
 | |
| {
 | |
|   return NumTracks(ttSubtitleFirst, ttSubtitleLast);
 | |
| }
 | |
| 
 | |
| bool cDevice::SetCurrentAudioTrack(eTrackType Type)
 | |
| {
 | |
|   if (ttNone < Type && Type <= ttDolbyLast) {
 | |
|      cMutexLock MutexLock(&mutexCurrentAudioTrack);
 | |
|      if (IS_DOLBY_TRACK(Type))
 | |
|         SetDigitalAudioDevice(true);
 | |
|      currentAudioTrack = Type;
 | |
|      if (player)
 | |
|         player->SetAudioTrack(currentAudioTrack, GetTrack(currentAudioTrack));
 | |
|      else
 | |
|         SetAudioTrackDevice(currentAudioTrack);
 | |
|      if (IS_AUDIO_TRACK(Type))
 | |
|         SetDigitalAudioDevice(false);
 | |
|      return true;
 | |
|      }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::SetCurrentSubtitleTrack(eTrackType Type, bool Manual)
 | |
| {
 | |
|   if (Type == ttNone || IS_SUBTITLE_TRACK(Type)) {
 | |
|      currentSubtitleTrack = Type;
 | |
|      autoSelectPreferredSubtitleLanguage = !Manual;
 | |
|      if (dvbSubtitleConverter)
 | |
|         dvbSubtitleConverter->Reset();
 | |
|      if (Type == ttNone && dvbSubtitleConverter) {
 | |
|         cMutexLock MutexLock(&mutexCurrentSubtitleTrack);
 | |
|         DELETENULL(dvbSubtitleConverter);
 | |
|         }
 | |
|      DELETENULL(liveSubtitle);
 | |
|      if (player)
 | |
|         player->SetSubtitleTrack(currentSubtitleTrack, GetTrack(currentSubtitleTrack));
 | |
|      else
 | |
|         SetSubtitleTrackDevice(currentSubtitleTrack);
 | |
|      if (currentSubtitleTrack != ttNone && !Replaying() && !Transferring()) {
 | |
|         const tTrackId *TrackId = GetTrack(currentSubtitleTrack);
 | |
|         if (TrackId && TrackId->id) {
 | |
|            liveSubtitle = new cLiveSubtitle(TrackId->id);
 | |
|            AttachReceiver(liveSubtitle);
 | |
|            }
 | |
|         }
 | |
|      return true;
 | |
|      }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void cDevice::EnsureAudioTrack(bool Force)
 | |
| {
 | |
|   if (keepTracks)
 | |
|      return;
 | |
|   if (Force || !availableTracks[currentAudioTrack].id) {
 | |
|      eTrackType PreferredTrack = ttAudioFirst;
 | |
|      int PreferredAudioChannel = 0;
 | |
|      int LanguagePreference = -1;
 | |
|      int StartCheck = Setup.CurrentDolby ? ttDolbyFirst : ttAudioFirst;
 | |
|      int EndCheck = ttDolbyLast;
 | |
|      for (int i = StartCheck; i <= EndCheck; i++) {
 | |
|          const tTrackId *TrackId = GetTrack(eTrackType(i));
 | |
|          int pos = 0;
 | |
|          if (TrackId && TrackId->id && I18nIsPreferredLanguage(Setup.AudioLanguages, TrackId->language, LanguagePreference, &pos)) {
 | |
|             PreferredTrack = eTrackType(i);
 | |
|             PreferredAudioChannel = pos;
 | |
|             }
 | |
|          if (Setup.CurrentDolby && i == ttDolbyLast) {
 | |
|             i = ttAudioFirst - 1;
 | |
|             EndCheck = ttAudioLast;
 | |
|             }
 | |
|          }
 | |
|      // Make sure we're set to an available audio track:
 | |
|      const tTrackId *Track = GetTrack(GetCurrentAudioTrack());
 | |
|      if (Force || !Track || !Track->id || PreferredTrack != GetCurrentAudioTrack()) {
 | |
|         if (!Force) // only log this for automatic changes
 | |
|            dsyslog("setting audio track to %d (%d)", PreferredTrack, PreferredAudioChannel);
 | |
|         SetCurrentAudioTrack(PreferredTrack);
 | |
|         SetAudioChannel(PreferredAudioChannel);
 | |
|         }
 | |
|      }
 | |
| }
 | |
| 
 | |
| void cDevice::EnsureSubtitleTrack(void)
 | |
| {
 | |
|   if (keepTracks)
 | |
|      return;
 | |
|   if (Setup.DisplaySubtitles) {
 | |
|      eTrackType PreferredTrack = ttNone;
 | |
|      int LanguagePreference = INT_MAX; // higher than the maximum possible value
 | |
|      for (int i = ttSubtitleFirst; i <= ttSubtitleLast; i++) {
 | |
|          const tTrackId *TrackId = GetTrack(eTrackType(i));
 | |
|          if (TrackId && TrackId->id && (I18nIsPreferredLanguage(Setup.SubtitleLanguages, TrackId->language, LanguagePreference) ||
 | |
|             (i == ttSubtitleFirst + 8 && !*TrackId->language && LanguagePreference == INT_MAX))) // compatibility mode for old subtitles plugin
 | |
|             PreferredTrack = eTrackType(i);
 | |
|          }
 | |
|      // Make sure we're set to an available subtitle track:
 | |
|      const tTrackId *Track = GetTrack(GetCurrentSubtitleTrack());
 | |
|      if (!Track || !Track->id || PreferredTrack != GetCurrentSubtitleTrack())
 | |
|         SetCurrentSubtitleTrack(PreferredTrack);
 | |
|      }
 | |
|   else
 | |
|      SetCurrentSubtitleTrack(ttNone);
 | |
| }
 | |
| 
 | |
| bool cDevice::CanReplay(void) const
 | |
| {
 | |
|   return HasDecoder();
 | |
| }
 | |
| 
 | |
| bool cDevice::SetPlayMode(ePlayMode PlayMode)
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| int64_t cDevice::GetSTC(void)
 | |
| {
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| void cDevice::TrickSpeed(int Speed, bool Forward)
 | |
| {
 | |
| }
 | |
| 
 | |
| void cDevice::Clear(void)
 | |
| {
 | |
|   Audios.ClearAudio();
 | |
|   if (dvbSubtitleConverter)
 | |
|      dvbSubtitleConverter->Reset();
 | |
| }
 | |
| 
 | |
| void cDevice::Play(void)
 | |
| {
 | |
|   Audios.MuteAudio(mute);
 | |
|   if (dvbSubtitleConverter)
 | |
|      dvbSubtitleConverter->Freeze(false);
 | |
| }
 | |
| 
 | |
| void cDevice::Freeze(void)
 | |
| {
 | |
|   Audios.MuteAudio(true);
 | |
|   if (dvbSubtitleConverter)
 | |
|      dvbSubtitleConverter->Freeze(true);
 | |
| }
 | |
| 
 | |
| void cDevice::Mute(void)
 | |
| {
 | |
|   Audios.MuteAudio(true);
 | |
| }
 | |
| 
 | |
| void cDevice::StillPicture(const uchar *Data, int Length)
 | |
| {
 | |
|   if (Data[0] == 0x47) {
 | |
|      // TS data
 | |
|      cTsToPes TsToPes;
 | |
|      uchar *buf = NULL;
 | |
|      int Size = 0;
 | |
|      while (Length >= TS_SIZE) {
 | |
|            int Pid = TsPid(Data);
 | |
|            if (Pid == PATPID)
 | |
|               patPmtParser.ParsePat(Data, TS_SIZE);
 | |
|            else if (patPmtParser.IsPmtPid(Pid))
 | |
|               patPmtParser.ParsePmt(Data, TS_SIZE);
 | |
|            else if (Pid == patPmtParser.Vpid()) {
 | |
|               if (TsPayloadStart(Data)) {
 | |
|                  int l;
 | |
|                  while (const uchar *p = TsToPes.GetPes(l)) {
 | |
|                        int Offset = Size;
 | |
|                        int NewSize = Size + l;
 | |
|                        if (uchar *NewBuffer = (uchar *)realloc(buf, NewSize)) {
 | |
|                           Size = NewSize;
 | |
|                           buf = NewBuffer;
 | |
|                           memcpy(buf + Offset, p, l);
 | |
|                           }
 | |
|                        else {
 | |
|                           LOG_ERROR_STR("out of memory");
 | |
|                           free(buf);
 | |
|                           return;
 | |
|                           }
 | |
|                        }
 | |
|                  TsToPes.Reset();
 | |
|                  }
 | |
|               TsToPes.PutTs(Data, TS_SIZE);
 | |
|               }
 | |
|            Length -= TS_SIZE;
 | |
|            Data += TS_SIZE;
 | |
|            }
 | |
|      int l;
 | |
|      while (const uchar *p = TsToPes.GetPes(l)) {
 | |
|            int Offset = Size;
 | |
|            int NewSize = Size + l;
 | |
|            if (uchar *NewBuffer = (uchar *)realloc(buf, NewSize)) {
 | |
|               Size = NewSize;
 | |
|               buf = NewBuffer;
 | |
|               memcpy(buf + Offset, p, l);
 | |
|               }
 | |
|            else {
 | |
|               esyslog("ERROR: out of memory");
 | |
|               free(buf);
 | |
|               return;
 | |
|               }
 | |
|            }
 | |
|      if (buf) {
 | |
|         StillPicture(buf, Size);
 | |
|         free(buf);
 | |
|         }
 | |
|      }
 | |
| }
 | |
| 
 | |
| bool cDevice::Replaying(void) const
 | |
| {
 | |
|   return player != NULL;
 | |
| }
 | |
| 
 | |
| bool cDevice::Transferring(void) const
 | |
| {
 | |
|   return cTransferControl::ReceiverDevice() != NULL;
 | |
| }
 | |
| 
 | |
| bool cDevice::AttachPlayer(cPlayer *Player)
 | |
| {
 | |
|   if (CanReplay()) {
 | |
|      if (player)
 | |
|         Detach(player);
 | |
|      DELETENULL(liveSubtitle);
 | |
|      DELETENULL(dvbSubtitleConverter);
 | |
|      patPmtParser.Reset();
 | |
|      player = Player;
 | |
|      if (!Transferring())
 | |
|         ClrAvailableTracks(false, true);
 | |
|      SetPlayMode(player->playMode);
 | |
|      player->device = this;
 | |
|      player->Activate(true);
 | |
|      return true;
 | |
|      }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void cDevice::Detach(cPlayer *Player)
 | |
| {
 | |
|   if (Player && player == Player) {
 | |
|      cPlayer *p = player;
 | |
|      player = NULL; // avoids recursive calls to Detach()
 | |
|      p->Activate(false);
 | |
|      p->device = NULL;
 | |
|      cMutexLock MutexLock(&mutexCurrentSubtitleTrack);
 | |
|      delete dvbSubtitleConverter;
 | |
|      dvbSubtitleConverter = NULL;
 | |
|      SetPlayMode(pmNone);
 | |
|      SetVideoDisplayFormat(eVideoDisplayFormat(Setup.VideoDisplayFormat));
 | |
|      PlayTs(NULL, 0);
 | |
|      patPmtParser.Reset();
 | |
|      Audios.ClearAudio();
 | |
|      isPlayingVideo = false;
 | |
|      }
 | |
| }
 | |
| 
 | |
| void cDevice::StopReplay(void)
 | |
| {
 | |
|   if (player) {
 | |
|      Detach(player);
 | |
|      if (IsPrimaryDevice())
 | |
|         cControl::Shutdown();
 | |
|      }
 | |
| }
 | |
| 
 | |
| bool cDevice::Poll(cPoller &Poller, int TimeoutMs)
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::Flush(int TimeoutMs)
 | |
| {
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| int cDevice::PlayVideo(const uchar *Data, int Length)
 | |
| {
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| int cDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
 | |
| {
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| int cDevice::PlaySubtitle(const uchar *Data, int Length)
 | |
| {
 | |
|   if (!dvbSubtitleConverter)
 | |
|      dvbSubtitleConverter = new cDvbSubtitleConverter;
 | |
|   return dvbSubtitleConverter->ConvertFragments(Data, Length);
 | |
| }
 | |
| 
 | |
| int cDevice::PlayPesPacket(const uchar *Data, int Length, bool VideoOnly)
 | |
| {
 | |
|   bool FirstLoop = true;
 | |
|   uchar c = Data[3];
 | |
|   const uchar *Start = Data;
 | |
|   const uchar *End = Start + Length;
 | |
|   while (Start < End) {
 | |
|         int d = End - Start;
 | |
|         int w = d;
 | |
|         switch (c) {
 | |
|           case 0xBE:          // padding stream, needed for MPEG1
 | |
|           case 0xE0 ... 0xEF: // video
 | |
|                isPlayingVideo = true;
 | |
|                w = PlayVideo(Start, d);
 | |
|                break;
 | |
|           case 0xC0 ... 0xDF: // audio
 | |
|                SetAvailableTrack(ttAudio, c - 0xC0, c);
 | |
|                if ((!VideoOnly || HasIBPTrickSpeed()) && c == availableTracks[currentAudioTrack].id) {
 | |
|                   w = PlayAudio(Start, d, c);
 | |
|                   if (FirstLoop)
 | |
|                      Audios.PlayAudio(Data, Length, c);
 | |
|                   }
 | |
|                break;
 | |
|           case 0xBD: { // private stream 1
 | |
|                int PayloadOffset = Data[8] + 9;
 | |
| 
 | |
|                // Compatibility mode for old subtitles plugin:
 | |
|                if ((Data[7] & 0x01) && (Data[PayloadOffset - 3] & 0x81) == 0x01 && Data[PayloadOffset - 2] == 0x81)
 | |
|                   PayloadOffset--;
 | |
| 
 | |
|                uchar SubStreamId = Data[PayloadOffset];
 | |
|                uchar SubStreamType = SubStreamId & 0xF0;
 | |
|                uchar SubStreamIndex = SubStreamId & 0x1F;
 | |
| 
 | |
|                // Compatibility mode for old VDR recordings, where 0xBD was only AC3:
 | |
| pre_1_3_19_PrivateStreamDetected:
 | |
|                if (pre_1_3_19_PrivateStream > MIN_PRE_1_3_19_PRIVATESTREAM) {
 | |
|                   SubStreamId = c;
 | |
|                   SubStreamType = 0x80;
 | |
|                   SubStreamIndex = 0;
 | |
|                   }
 | |
|                else if (pre_1_3_19_PrivateStream)
 | |
|                   pre_1_3_19_PrivateStream--; // every known PS1 packet counts down towards 0 to recover from glitches...
 | |
|                switch (SubStreamType) {
 | |
|                  case 0x20: // SPU
 | |
|                  case 0x30: // SPU
 | |
|                       SetAvailableTrack(ttSubtitle, SubStreamIndex, SubStreamId);
 | |
|                       if ((!VideoOnly || HasIBPTrickSpeed()) && currentSubtitleTrack != ttNone && SubStreamId == availableTracks[currentSubtitleTrack].id)
 | |
|                          w = PlaySubtitle(Start, d);
 | |
|                       break;
 | |
|                  case 0x80: // AC3 & DTS
 | |
|                       if (Setup.UseDolbyDigital) {
 | |
|                          SetAvailableTrack(ttDolby, SubStreamIndex, SubStreamId);
 | |
|                          if ((!VideoOnly || HasIBPTrickSpeed()) && SubStreamId == availableTracks[currentAudioTrack].id) {
 | |
|                             w = PlayAudio(Start, d, SubStreamId);
 | |
|                             if (FirstLoop)
 | |
|                                Audios.PlayAudio(Data, Length, SubStreamId);
 | |
|                             }
 | |
|                          }
 | |
|                       break;
 | |
|                  case 0xA0: // LPCM
 | |
|                       SetAvailableTrack(ttAudio, SubStreamIndex, SubStreamId);
 | |
|                       if ((!VideoOnly || HasIBPTrickSpeed()) && SubStreamId == availableTracks[currentAudioTrack].id) {
 | |
|                          w = PlayAudio(Start, d, SubStreamId);
 | |
|                          if (FirstLoop)
 | |
|                             Audios.PlayAudio(Data, Length, SubStreamId);
 | |
|                          }
 | |
|                       break;
 | |
|                  default:
 | |
|                       // Compatibility mode for old VDR recordings, where 0xBD was only AC3:
 | |
|                       if (pre_1_3_19_PrivateStream <= MIN_PRE_1_3_19_PRIVATESTREAM) {
 | |
|                          dsyslog("unknown PS1 packet, substream id = %02X (counter is at %d)", SubStreamId, pre_1_3_19_PrivateStream);
 | |
|                          pre_1_3_19_PrivateStream += 2; // ...and every unknown PS1 packet counts up (the very first one counts twice, but that's ok)
 | |
|                          if (pre_1_3_19_PrivateStream > MIN_PRE_1_3_19_PRIVATESTREAM) {
 | |
|                             dsyslog("switching to pre 1.3.19 Dolby Digital compatibility mode - substream id = %02X", SubStreamId);
 | |
|                             ClrAvailableTracks();
 | |
|                             pre_1_3_19_PrivateStream = MIN_PRE_1_3_19_PRIVATESTREAM + 1;
 | |
|                             goto pre_1_3_19_PrivateStreamDetected;
 | |
|                             }
 | |
|                          }
 | |
|                  }
 | |
|                }
 | |
|                break;
 | |
|           default:
 | |
|                ;//esyslog("ERROR: unexpected packet id %02X", c);
 | |
|           }
 | |
|         if (w > 0)
 | |
|            Start += w;
 | |
|         else {
 | |
|            if (Start != Data)
 | |
|               esyslog("ERROR: incomplete PES packet write!");
 | |
|            return Start == Data ? w : Start - Data;
 | |
|            }
 | |
|         FirstLoop = false;
 | |
|         }
 | |
|   return Length;
 | |
| }
 | |
| 
 | |
| int cDevice::PlayPes(const uchar *Data, int Length, bool VideoOnly)
 | |
| {
 | |
|   if (!Data) {
 | |
|      if (dvbSubtitleConverter)
 | |
|         dvbSubtitleConverter->Reset();
 | |
|      return 0;
 | |
|      }
 | |
|   int i = 0;
 | |
|   while (i <= Length - 6) {
 | |
|         if (Data[i] == 0x00 && Data[i + 1] == 0x00 && Data[i + 2] == 0x01) {
 | |
|            int l = PesLength(Data + i);
 | |
|            if (i + l > Length) {
 | |
|               esyslog("ERROR: incomplete PES packet!");
 | |
|               return Length;
 | |
|               }
 | |
|            int w = PlayPesPacket(Data + i, l, VideoOnly);
 | |
|            if (w > 0)
 | |
|               i += l;
 | |
|            else
 | |
|               return i == 0 ? w : i;
 | |
|            }
 | |
|         else
 | |
|            i++;
 | |
|         }
 | |
|   if (i < Length)
 | |
|      esyslog("ERROR: leftover PES data!");
 | |
|   return Length;
 | |
| }
 | |
| 
 | |
| int cDevice::PlayTsVideo(const uchar *Data, int Length)
 | |
| {
 | |
|   // Video PES has no explicit length, so we can only determine the end of
 | |
|   // a PES packet when the next TS packet that starts a payload comes in:
 | |
|   if (TsPayloadStart(Data)) {
 | |
|      int l;
 | |
|      while (const uchar *p = tsToPesVideo.GetPes(l)) {
 | |
|            int w = PlayVideo(p, l);
 | |
|            if (w <= 0) {
 | |
|               tsToPesVideo.SetRepeatLast();
 | |
|               return w;
 | |
|               }
 | |
|            }
 | |
|      tsToPesVideo.Reset();
 | |
|      }
 | |
|   tsToPesVideo.PutTs(Data, Length);
 | |
|   return Length;
 | |
| }
 | |
| 
 | |
| int cDevice::PlayTsAudio(const uchar *Data, int Length)
 | |
| {
 | |
|   // Audio PES always has an explicit length and consists of single packets:
 | |
|   int l;
 | |
|   if (const uchar *p = tsToPesAudio.GetPes(l)) {
 | |
|      int w = PlayAudio(p, l, p[3]);
 | |
|      if (w <= 0) {
 | |
|         tsToPesAudio.SetRepeatLast();
 | |
|         return w;
 | |
|         }
 | |
|      tsToPesAudio.Reset();
 | |
|      }
 | |
|   tsToPesAudio.PutTs(Data, Length);
 | |
|   return Length;
 | |
| }
 | |
| 
 | |
| int cDevice::PlayTsSubtitle(const uchar *Data, int Length)
 | |
| {
 | |
|   if (!dvbSubtitleConverter)
 | |
|      dvbSubtitleConverter = new cDvbSubtitleConverter;
 | |
|   tsToPesSubtitle.PutTs(Data, Length);
 | |
|   int l;
 | |
|   if (const uchar *p = tsToPesSubtitle.GetPes(l)) {
 | |
|      dvbSubtitleConverter->Convert(p, l);
 | |
|      tsToPesSubtitle.Reset();
 | |
|      }
 | |
|   return Length;
 | |
| }
 | |
| 
 | |
| //TODO detect and report continuity errors?
 | |
| int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
 | |
| {
 | |
|   int Played = 0;
 | |
|   if (!Data) {
 | |
|      tsToPesVideo.Reset();
 | |
|      tsToPesAudio.Reset();
 | |
|      tsToPesSubtitle.Reset();
 | |
|      }
 | |
|   else if (Length < TS_SIZE) {
 | |
|      esyslog("ERROR: skipped %d bytes of TS fragment", Length);
 | |
|      return Length;
 | |
|      }
 | |
|   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);
 | |
|               return Played + Skipped;
 | |
|               }
 | |
|            int Pid = TsPid(Data);
 | |
|            if (TsHasPayload(Data)) { // silently ignore TS packets w/o payload
 | |
|               int PayloadOffset = TsPayloadOffset(Data);
 | |
|               if (PayloadOffset < TS_SIZE) {
 | |
|                  if (Pid == PATPID)
 | |
|                     patPmtParser.ParsePat(Data, TS_SIZE);
 | |
|                  else if (patPmtParser.IsPmtPid(Pid))
 | |
|                     patPmtParser.ParsePmt(Data, TS_SIZE);
 | |
|                  else if (Pid == patPmtParser.Vpid()) {
 | |
|                     isPlayingVideo = true;
 | |
|                     int w = PlayTsVideo(Data, TS_SIZE);
 | |
|                     if (w < 0)
 | |
|                        return Played ? Played : w;
 | |
|                     if (w == 0)
 | |
|                        break;
 | |
|                     }
 | |
|                  else if (Pid == availableTracks[currentAudioTrack].id) {
 | |
|                     if (!VideoOnly || HasIBPTrickSpeed()) {
 | |
|                        int w = PlayTsAudio(Data, TS_SIZE);
 | |
|                        if (w < 0)
 | |
|                           return Played ? Played : w;
 | |
|                        if (w == 0)
 | |
|                           break;
 | |
|                        Audios.PlayTsAudio(Data, TS_SIZE);
 | |
|                        }
 | |
|                     }
 | |
|                  else if (Pid == availableTracks[currentSubtitleTrack].id) {
 | |
|                     if (!VideoOnly || HasIBPTrickSpeed())
 | |
|                        PlayTsSubtitle(Data, TS_SIZE);
 | |
|                     }
 | |
|                  }
 | |
|               }
 | |
|            else if (Pid == patPmtParser.Ppid()) {
 | |
|               int w = PlayTsVideo(Data, TS_SIZE);
 | |
|               if (w < 0)
 | |
|                  return Played ? Played : w;
 | |
|               if (w == 0)
 | |
|                  break;
 | |
|               }
 | |
|            Played += TS_SIZE;
 | |
|            Length -= TS_SIZE;
 | |
|            Data += TS_SIZE;
 | |
|            }
 | |
|      }
 | |
|   return Played;
 | |
| }
 | |
| 
 | |
| int cDevice::Priority(void) const
 | |
| {
 | |
|   int priority = IDLEPRIORITY;
 | |
|   if (IsPrimaryDevice() && !Replaying() && HasProgramme())
 | |
|      priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
 | |
|   cMutexLock MutexLock(&mutexReceiver);
 | |
|   for (int i = 0; i < MAXRECEIVERS; i++) {
 | |
|       if (receiver[i])
 | |
|          priority = max(receiver[i]->priority, priority);
 | |
|       }
 | |
|   return priority;
 | |
| }
 | |
| 
 | |
| bool cDevice::Ready(void)
 | |
| {
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool cDevice::Receiving(bool Dummy) const
 | |
| {
 | |
|   cMutexLock MutexLock(&mutexReceiver);
 | |
|   for (int i = 0; i < MAXRECEIVERS; i++) {
 | |
|       if (receiver[i])
 | |
|          return true;
 | |
|       }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| #define TS_SCRAMBLING_TIMEOUT     3 // seconds to wait until a TS becomes unscrambled
 | |
| #define TS_SCRAMBLING_TIME_OK    10 // seconds before a Channel/CAM combination is marked as known to decrypt
 | |
| 
 | |
| void cDevice::Action(void)
 | |
| {
 | |
|   time_t LastScrambledPacket = 0;
 | |
|   if (Running() && OpenDvr()) {
 | |
|      while (Running()) {
 | |
|            // Read data from the DVR device:
 | |
|            uchar *b = NULL;
 | |
|            if (GetTSPacket(b)) {
 | |
|               if (b) {
 | |
|                  int Pid = TsPid(b);
 | |
|                  // Check whether the TS packets are scrambled:
 | |
|                  bool DetachReceivers = false;
 | |
|                  bool DescramblingOk = false;
 | |
|                  int CamSlotNumber = 0;
 | |
|                  cCamSlot *cs = NULL;
 | |
|                  if (startScrambleDetection) {
 | |
|                     cs = CamSlot();
 | |
|                     CamSlotNumber = cs ? cs->MasterSlotNumber() : 0;
 | |
|                     if (CamSlotNumber) {
 | |
|                        if (LastScrambledPacket < startScrambleDetection)
 | |
|                           LastScrambledPacket = startScrambleDetection;
 | |
|                        time_t Now = time(NULL);
 | |
|                        if (TsIsScrambled(b)) {
 | |
|                           LastScrambledPacket = Now;
 | |
|                           if (Now - startScrambleDetection > scramblingTimeout)
 | |
|                              DetachReceivers = true;
 | |
|                           }
 | |
|                        if (Now - LastScrambledPacket > TS_SCRAMBLING_TIME_OK)
 | |
|                           DescramblingOk = true;
 | |
|                        }
 | |
|                     }
 | |
|                  // Distribute the packet to all attached receivers:
 | |
|                  Lock();
 | |
|                  for (int i = 0; i < MAXRECEIVERS; i++) {
 | |
|                      if (receiver[i] && receiver[i]->WantsPid(Pid)) {
 | |
|                         if (DetachReceivers && cs && (!cs->IsActivating() || receiver[i]->Priority() >= LIVEPRIORITY)) {
 | |
|                            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) {
 | |
|                            dsyslog("CAM %d: decrypts channel %s", CamSlotNumber, *receiver[i]->ChannelID().ToString());
 | |
|                            ChannelCamRelations.SetDecrypt(receiver[i]->ChannelID(), CamSlotNumber);
 | |
|                            startScrambleDetection = 0;
 | |
|                            }
 | |
|                         }
 | |
|                      }
 | |
|                  Unlock();
 | |
|                  }
 | |
|               }
 | |
|            else
 | |
|               break;
 | |
|            }
 | |
|      CloseDvr();
 | |
|      }
 | |
| }
 | |
| 
 | |
| bool cDevice::OpenDvr(void)
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void cDevice::CloseDvr(void)
 | |
| {
 | |
| }
 | |
| 
 | |
| bool cDevice::GetTSPacket(uchar *&Data)
 | |
| {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool cDevice::AttachReceiver(cReceiver *Receiver)
 | |
| {
 | |
|   if (!Receiver)
 | |
|      return false;
 | |
|   if (Receiver->device == this)
 | |
|      return true;
 | |
| // activate the following line if you need it - actually the driver should be fixed!
 | |
| //#define WAIT_FOR_TUNER_LOCK
 | |
| #ifdef WAIT_FOR_TUNER_LOCK
 | |
| #define TUNER_LOCK_TIMEOUT 5000 // ms
 | |
|   if (!HasLock(TUNER_LOCK_TIMEOUT)) {
 | |
|      esyslog("ERROR: device %d has no lock, can't attach receiver!", CardIndex() + 1);
 | |
|      return false;
 | |
|      }
 | |
| #endif
 | |
|   cMutexLock MutexLock(&mutexReceiver);
 | |
|   for (int i = 0; i < MAXRECEIVERS; i++) {
 | |
|       if (!receiver[i]) {
 | |
|          for (int n = 0; n < Receiver->numPids; n++) {
 | |
|              if (!AddPid(Receiver->pids[n])) {
 | |
|                 for ( ; n-- > 0; )
 | |
|                     DelPid(Receiver->pids[n]);
 | |
|                 return false;
 | |
|                 }
 | |
|              }
 | |
|          Receiver->Activate(true);
 | |
|          Lock();
 | |
|          Receiver->device = this;
 | |
|          receiver[i] = Receiver;
 | |
|          Unlock();
 | |
|          if (camSlot && Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver
 | |
|             camSlot->StartDecrypting();
 | |
|             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;
 | |
|          }
 | |
|       }
 | |
|   esyslog("ERROR: no free receiver slot!");
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void cDevice::Detach(cReceiver *Receiver)
 | |
| {
 | |
|   if (!Receiver || Receiver->device != this)
 | |
|      return;
 | |
|   bool receiversLeft = false;
 | |
|   cMutexLock MutexLock(&mutexReceiver);
 | |
|   for (int i = 0; i < MAXRECEIVERS; i++) {
 | |
|       if (receiver[i] == Receiver) {
 | |
|          Lock();
 | |
|          receiver[i] = NULL;
 | |
|          Receiver->device = NULL;
 | |
|          Unlock();
 | |
|          Receiver->Activate(false);
 | |
|          for (int n = 0; n < Receiver->numPids; n++)
 | |
|              DelPid(Receiver->pids[n]);
 | |
|          }
 | |
|       else if (receiver[i])
 | |
|          receiversLeft = true;
 | |
|       }
 | |
|   if (camSlot) {
 | |
|      if (Receiver->priority > MINPRIORITY) { // priority check to avoid an infinite loop with the CAM slot's caPidReceiver
 | |
|         camSlot->StartDecrypting();
 | |
|         if (!camSlot->IsDecrypting() && !camSlot->IsActivating())
 | |
|            camSlot->Assign(NULL);
 | |
|         }
 | |
|      }
 | |
|   if (!receiversLeft)
 | |
|      Cancel(-1);
 | |
| }
 | |
| 
 | |
| void cDevice::DetachAll(int Pid)
 | |
| {
 | |
|   if (Pid) {
 | |
|      cMutexLock MutexLock(&mutexReceiver);
 | |
|      for (int i = 0; i < MAXRECEIVERS; i++) {
 | |
|          cReceiver *Receiver = receiver[i];
 | |
|          if (Receiver && Receiver->WantsPid(Pid))
 | |
|             Detach(Receiver);
 | |
|          }
 | |
|      }
 | |
| }
 | |
| 
 | |
| void cDevice::DetachAllReceivers(void)
 | |
| {
 | |
|   cMutexLock MutexLock(&mutexReceiver);
 | |
|   for (int i = 0; i < MAXRECEIVERS; i++)
 | |
|       Detach(receiver[i]);
 | |
| }
 | |
| 
 | |
| // --- cTSBuffer -------------------------------------------------------------
 | |
| 
 | |
| cTSBuffer::cTSBuffer(int File, int Size, int CardIndex)
 | |
| {
 | |
|   SetDescription("device %d TS buffer", CardIndex);
 | |
|   f = File;
 | |
|   cardIndex = CardIndex;
 | |
|   delivered = false;
 | |
|   ringBuffer = new cRingBufferLinear(Size, TS_SIZE, true, "TS");
 | |
|   ringBuffer->SetTimeouts(100, 100);
 | |
|   ringBuffer->SetIoThrottle();
 | |
|   Start();
 | |
| }
 | |
| 
 | |
| cTSBuffer::~cTSBuffer()
 | |
| {
 | |
|   Cancel(3);
 | |
|   delete ringBuffer;
 | |
| }
 | |
| 
 | |
| void cTSBuffer::Action(void)
 | |
| {
 | |
|   if (ringBuffer) {
 | |
|      bool firstRead = true;
 | |
|      cPoller Poller(f);
 | |
|      while (Running()) {
 | |
|            if (firstRead || Poller.Poll(100)) {
 | |
|               firstRead = false;
 | |
|               int r = ringBuffer->Read(f);
 | |
|               if (r < 0 && FATALERRNO) {
 | |
|                  if (errno == EOVERFLOW)
 | |
|                     esyslog("ERROR: driver buffer overflow on device %d", cardIndex);
 | |
|                  else {
 | |
|                     LOG_ERROR;
 | |
|                     break;
 | |
|                     }
 | |
|                  }
 | |
|               cCondWait::SleepMs(10); // avoids small chunks of data, which cause high CPU usage, esp. on ARM CPUs
 | |
|               }
 | |
|            }
 | |
|      }
 | |
| }
 | |
| 
 | |
| uchar *cTSBuffer::Get(int *Available)
 | |
| {
 | |
|   int Count = 0;
 | |
|   if (delivered) {
 | |
|      ringBuffer->Del(TS_SIZE);
 | |
|      delivered = false;
 | |
|      }
 | |
|   uchar *p = ringBuffer->Get(Count);
 | |
|   if (p && Count >= TS_SIZE) {
 | |
|      if (*p != TS_SYNC_BYTE) {
 | |
|         for (int i = 1; i < Count; i++) {
 | |
|             if (p[i] == TS_SYNC_BYTE) {
 | |
|                Count = i;
 | |
|                break;
 | |
|                }
 | |
|             }
 | |
|         ringBuffer->Del(Count);
 | |
|         esyslog("ERROR: skipped %d bytes to sync on TS packet on device %d", Count, cardIndex);
 | |
|         return NULL;
 | |
|         }
 | |
|      delivered = true;
 | |
|      if (Available)
 | |
|         *Available = Count;
 | |
|      return p;
 | |
|      }
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| void cTSBuffer::Skip(int Count)
 | |
| {
 | |
|   ringBuffer->Del(Count);
 | |
|   delivered = false;
 | |
| }
 |