vdr  1.7.31
dvbhdffdevice.c
Go to the documentation of this file.
1 /*
2  * dvbhdffdevice.c: The DVB HD Full Featured device interface
3  *
4  * See the README file for copyright information and how to reach the author.
5  *
6  * $Id: dvbhdffdevice.c 1.43 2012/05/08 11:40:32 kls Exp $
7  */
8 
9 #include <stdint.h>
10 
11 #include "dvbhdffdevice.h"
12 #include <errno.h>
13 #include <limits.h>
14 #include <libsi/si.h>
15 #include <linux/videodev2.h>
16 #include <linux/dvb/audio.h>
17 #include <linux/dvb/dmx.h>
18 #include <linux/dvb/video.h>
19 #include <sys/ioctl.h>
20 #include <sys/mman.h>
21 #include <vdr/eitscan.h>
22 #include <vdr/transfer.h>
23 #include "hdffosd.h"
24 #include "setup.h"
25 
26 // --- cDvbHdFfDevice ----------------------------------------------------------
27 
29 
30 cDvbHdFfDevice::cDvbHdFfDevice(int Adapter, int Frontend)
31 :cDvbDevice(Adapter, Frontend)
32 {
33  spuDecoder = NULL;
34  audioChannel = 0;
35  playMode = pmNone;
36  mHdffCmdIf = NULL;
37 
38  // Devices that are only present on cards with decoders:
39 
41  fd_video = DvbOpen(DEV_DVB_VIDEO, adapter, frontend, O_RDWR | O_NONBLOCK);
42  fd_audio = DvbOpen(DEV_DVB_AUDIO, adapter, frontend, O_RDWR | O_NONBLOCK);
43 
44  //TODO missing /dev/video offset calculation
45 
46  isHdffPrimary = false;
47  if (devHdffOffset < 0) {
49  isHdffPrimary = true;
51 
52  /* reset some stuff in case the VDR was killed before and had no chance
53  to clean up. */
55 
58 
63 
64  ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX);
66  mHdffCmdIf->CmdAvEnableSync(0, true);
67  mHdffCmdIf->CmdAvSetPlayMode(0, true);
68  /* reset done */
69 
74 
75  HdffHdmiConfig_t hdmiConfig;
76  memset(&hdmiConfig, 0, sizeof(hdmiConfig));
77  hdmiConfig.TransmitAudio = true;
78  hdmiConfig.ForceDviMode = false;
79  hdmiConfig.CecEnabled = gHdffSetup.CecEnabled;
80  strcpy(hdmiConfig.CecDeviceName, "VDR");
82  mHdffCmdIf->CmdHdmiConfigure(&hdmiConfig);
83 
86  }
87 }
88 
90 {
91  delete spuDecoder;
92  if (isHdffPrimary)
93  {
95  {
97  }
98  delete mHdffCmdIf;
99  }
100  // We're not explicitly closing any device files here, since this sometimes
101  // caused segfaults. Besides, the program is about to terminate anyway...
102 }
103 
105 {
106  if (On)
109 }
110 
112 {
113  return isHdffPrimary;
114 }
115 
117 {
118  if (!spuDecoder && IsPrimaryDevice())
119  spuDecoder = new cDvbSpuDecoder();
120  return spuDecoder;
121 }
122 
123 uchar *cDvbHdFfDevice::GrabImage(int &Size, bool Jpeg, int Quality, int SizeX, int SizeY)
124 {
125  //TODO
126  return NULL;
127 }
128 
130 {
131  //TODO???
132  cDevice::SetVideoDisplayFormat(VideoDisplayFormat);
133 }
134 
135 void cDvbHdFfDevice::SetVideoFormat(bool VideoFormat16_9)
136 {
137  HdffVideoFormat_t videoFormat;
138  videoFormat.AutomaticEnabled = true;
139  videoFormat.AfdEnabled = true;
140  videoFormat.TvFormat = (HdffTvFormat_t) gHdffSetup.TvFormat;
142  mHdffCmdIf->CmdAvSetVideoFormat(0, &videoFormat);
143 }
144 
146 {
147  eVideoSystem VideoSystem = vsPAL;
148  if (fd_video >= 0) {
149  video_size_t vs;
150  if (ioctl(fd_video, VIDEO_GET_SIZE, &vs) == 0) {
151  if (vs.h == 480 || vs.h == 240)
152  VideoSystem = vsNTSC;
153  }
154  else
155  LOG_ERROR;
156  }
157  return VideoSystem;
158 }
159 
160 void cDvbHdFfDevice::GetVideoSize(int &Width, int &Height, double &VideoAspect)
161 {
162  if (fd_video >= 0) {
163  video_size_t vs;
164  if (ioctl(fd_video, VIDEO_GET_SIZE, &vs) == 0) {
165  Width = vs.w;
166  Height = vs.h;
167  switch (vs.aspect_ratio) {
168  default:
169  case VIDEO_FORMAT_4_3: VideoAspect = 4.0 / 3.0; break;
170  case VIDEO_FORMAT_16_9: VideoAspect = 16.0 / 9.0; break;
171  case VIDEO_FORMAT_221_1: VideoAspect = 2.21; break;
172  }
173  return;
174  }
175  else
176  LOG_ERROR;
177  }
178  cDevice::GetVideoSize(Width, Height, VideoAspect);
179 }
180 
181 void cDvbHdFfDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect)
182 {
183  gHdffSetup.GetOsdSize(Width, Height, PixelAspect);
184 }
185 
186 bool cDvbHdFfDevice::SetPid(cPidHandle *Handle, int Type, bool On)
187 {
188  //printf("SetPid Type %d, On %d, PID %5d, streamtype %d, handle %d, used %d\n", Type, On, Handle->pid, Handle->streamType, Handle->handle, Handle->used);
189  if (Handle->pid) {
190  dmx_pes_filter_params pesFilterParams;
191  memset(&pesFilterParams, 0, sizeof(pesFilterParams));
192  if (On) {
193  if (Handle->handle < 0) {
194  Handle->handle = DvbOpen(DEV_DVB_DEMUX, adapter, frontend, O_RDWR | O_NONBLOCK, true);
195  if (Handle->handle < 0) {
196  LOG_ERROR;
197  return false;
198  }
199  }
200  if (Type == ptPcr)
201  mHdffCmdIf->CmdAvSetPcrPid(0, Handle->pid);
202  else if (Type == ptVideo) {
203  if (Handle->streamType == 0x1B)
205  else
207  }
208  else if (Type == ptAudio) {
209  if (Handle->streamType == 0x03)
211  else if (Handle->streamType == 0x04)
213  else if (Handle->streamType == SI::AC3DescriptorTag)
215  else if (Handle->streamType == SI::EnhancedAC3DescriptorTag)
217  else if (Handle->streamType == 0x0F)
219  else if (Handle->streamType == 0x11)
221  else
223  }
224  if (!(Type <= ptDolby && Handle->used <= 1)) {
225  pesFilterParams.pid = Handle->pid;
226  pesFilterParams.input = DMX_IN_FRONTEND;
227  pesFilterParams.output = DMX_OUT_TS_TAP;
228  pesFilterParams.pes_type= DMX_PES_OTHER;
229  pesFilterParams.flags = DMX_IMMEDIATE_START;
230  if (ioctl(Handle->handle, DMX_SET_PES_FILTER, &pesFilterParams) < 0) {
231  LOG_ERROR;
232  return false;
233  }
234  }
235  }
236  else if (!Handle->used) {
237  CHECK(ioctl(Handle->handle, DMX_STOP));
238  if (Type == ptPcr)
239  mHdffCmdIf->CmdAvSetPcrPid(0, 0);
240  else if (Type == ptVideo)
242  else if (Type == ptAudio)
244  else if (Type == ptDolby)
246  //TODO missing setting to 0x1FFF??? see cDvbDevice::SetPid()
247  close(Handle->handle);
248  Handle->handle = -1;
249  }
250  }
251  return true;
252 }
253 
255 {
256  // Turn off live PIDs:
257 
260  DetachAll(pidHandles[ptPcr].pid);
262  DelPid(pidHandles[ptAudio].pid);
263  DelPid(pidHandles[ptVideo].pid);
264  DelPid(pidHandles[ptPcr].pid, ptPcr);
266  DelPid(pidHandles[ptDolby].pid);
267 }
268 
269 bool cDvbHdFfDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
270 {
271  int apid = Channel->Apid(0);
272  int vpid = Channel->Vpid();
273  int dpid = Channel->Dpid(0);
274 
275  bool DoTune = !IsTunedToTransponder(Channel);
276 
277  bool pidHandlesVideo = pidHandles[ptVideo].pid == vpid;
278  bool pidHandlesAudio = pidHandles[ptAudio].pid == apid;
279 
280  bool TurnOffLivePIDs = DoTune
281  || !IsPrimaryDevice()
282  || LiveView // for a new live view the old PIDs need to be turned off
283  || pidHandlesVideo // for recording the PIDs must be shifted from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER
284  ;
285 
286  bool StartTransferMode = IsPrimaryDevice() && !DoTune
287  && (LiveView && HasPid(vpid ? vpid : apid) && (!pidHandlesVideo || (!pidHandlesAudio && (dpid ? pidHandles[ptAudio].pid != dpid : true)))// the PID is already set as DMX_PES_OTHER
288  || !LiveView && (pidHandlesVideo || pidHandlesAudio) // a recording is going to shift the PIDs from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER
289  );
291  StartTransferMode |= LiveView && IsPrimaryDevice() && Channel->Ca() >= CA_ENCRYPTED_MIN;
292 
293  //printf("SetChannelDevice Transfer %d, Live %d\n", StartTransferMode, LiveView);
294 
295  bool TurnOnLivePIDs = !StartTransferMode && LiveView;
296 
297  // Turn off live PIDs if necessary:
298 
299  if (TurnOffLivePIDs)
300  TurnOffLiveMode(LiveView);
301 
302  // Set the tuner:
303 
304  if (!cDvbDevice::SetChannelDevice(Channel, LiveView))
305  return false;
306 
307  // PID settings:
308 
309  if (TurnOnLivePIDs) {
310  if (!(AddPid(Channel->Ppid(), ptPcr) && AddPid(vpid, ptVideo, Channel->Vtype()) && AddPid(apid ? apid : dpid, ptAudio, apid ? 0 : Channel->Dtype(0)))) {
311  esyslog("ERROR: failed to set PIDs for channel %d on device %d", Channel->Number(), CardIndex() + 1);
312  return false;
313  }
314  }
315  else if (StartTransferMode)
316  cControl::Launch(new cTransferControl(this, Channel));
317 
318  return true;
319 }
320 
322 {
323  return audioChannel;
324 }
325 
327 {
328  mHdffCmdIf->CmdAvSetAudioChannel(AudioChannel);
329  audioChannel = AudioChannel;
330 }
331 
333 {
334  mHdffCmdIf->CmdMuxSetVolume(Volume * 100 / 255);
335 }
336 
338 {
339  // not needed
340 }
341 
343 {
344  //printf("SetAudioTrackDevice %d\n", Type);
345  const tTrackId *TrackId = GetTrack(Type);
346  if (TrackId && TrackId->id) {
347  int streamType = 0;
349  if (channel) {
350  if (IS_AUDIO_TRACK(Type))
351  streamType = channel->Atype(Type - ttAudioFirst);
352  else if (IS_DOLBY_TRACK(Type))
353  streamType = channel->Dtype(Type - ttDolbyFirst);
354  }
355  //printf("SetAudioTrackDevice new %d %d, current %d\n", TrackId->id, streamType, pidHandles[ptAudio].pid);
356  if (pidHandles[ptAudio].pid && pidHandles[ptAudio].pid != TrackId->id) {
358  if (CamSlot())
359  CamSlot()->SetPid(pidHandles[ptAudio].pid, false);
360  pidHandles[ptAudio].pid = TrackId->id;
361  pidHandles[ptAudio].streamType = streamType;
362  SetPid(&pidHandles[ptAudio], ptAudio, true);
363  if (CamSlot()) {
364  CamSlot()->SetPid(pidHandles[ptAudio].pid, true);
366  }
367  }
368  }
369 }
370 
372 {
373  return cDevice::CanReplay();
374 }
375 
377 {
378  if (PlayMode == pmNone) {
381 
383  mHdffCmdIf->CmdAvSetPcrPid(0, 0);
386 
387  ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX);
389  mHdffCmdIf->CmdAvEnableSync(0, true);
390  mHdffCmdIf->CmdAvSetPlayMode(0, true);
391  }
392  else {
393  if (playMode == pmNone)
394  TurnOffLiveMode(true);
395 
397  mHdffCmdIf->CmdAvSetStc(0, 100000);
398  mHdffCmdIf->CmdAvEnableSync(0, true);
400 
401  playVideoPid = -1;
402  playAudioPid = -1;
403  audioCounter = 0;
404  videoCounter = 0;
405  freezed = false;
406  trickMode = false;
407 
409  ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY);
410  }
411  playMode = PlayMode;
412  return true;
413 }
414 
416 {
417  if (fd_video >= 0) {
418  uint64_t pts;
419  if (ioctl(fd_video, VIDEO_GET_PTS, &pts) == -1) {
420  esyslog("ERROR: pts %d: %m", CardIndex() + 1);
421  return -1;
422  }
423  return pts;
424  }
425  if (fd_audio >= 0) {
426  uint64_t pts;
427  if (ioctl(fd_audio, AUDIO_GET_PTS, &pts) == -1) {
428  esyslog("ERROR: pts %d: %m", CardIndex() + 1);
429  return -1;
430  }
431  return pts;
432  }
433  return -1;
434 }
435 
437 {
438  freezed = false;
439  mHdffCmdIf->CmdAvEnableSync(0, false);
441  playAudioPid = -1;
442  if (Speed > 0)
443  mHdffCmdIf->CmdAvSetVideoSpeed(0, 100 / Speed);
444  trickMode = true;
445 }
446 
448 {
449  CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
452  playVideoPid = -1;
453  playAudioPid = -1;
454  cDevice::Clear();
455 }
456 
458 {
459  freezed = false;
460  trickMode = false;
461  mHdffCmdIf->CmdAvEnableSync(0, true);
464  cDevice::Play();
465 }
466 
468 {
469  freezed = true;
472  cDevice::Freeze();
473 }
474 
476 {
477  //TODO???
478  cDevice::Mute();
479 }
480 
482 {
483  switch (Vtype) {
484  case 0x01: return HDFF_VIDEO_STREAM_MPEG1;
485  case 0x02: return HDFF_VIDEO_STREAM_MPEG2;
486  case 0x1B: return HDFF_VIDEO_STREAM_H264;
487  default: return HDFF_VIDEO_STREAM_MPEG2; // fallback to MPEG2
488  }
489 }
490 
491 void cDvbHdFfDevice::StillPicture(const uchar *Data, int Length)
492 {
493  if (!Data || Length < TS_SIZE)
494  return;
495  if (Data[0] == 0x47) {
496  // TS data
497  cDevice::StillPicture(Data, Length);
498  }
499  else if (Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01 && (Data[3] & 0xF0) == 0xE0) {
500  // PES data
501  char *buf = MALLOC(char, Length);
502  if (!buf)
503  return;
504  int i = 0;
505  int blen = 0;
506  while (i < Length - 6) {
507  if (Data[i] == 0x00 && Data[i + 1] == 0x00 && Data[i + 2] == 0x01) {
508  int len = Data[i + 4] * 256 + Data[i + 5];
509  if ((Data[i + 3] & 0xF0) == 0xE0) { // video packet
510  // skip PES header
511  int offs = i + 6;
512  // skip header extension
513  if ((Data[i + 6] & 0xC0) == 0x80) {
514  // MPEG-2 PES header
515  if (Data[i + 8] >= Length)
516  break;
517  offs += 3;
518  offs += Data[i + 8];
519  len -= 3;
520  len -= Data[i + 8];
521  if (len < 0 || offs + len > Length)
522  break;
523  }
524  else {
525  // MPEG-1 PES header
526  while (offs < Length && len > 0 && Data[offs] == 0xFF) {
527  offs++;
528  len--;
529  }
530  if (offs <= Length - 2 && len >= 2 && (Data[offs] & 0xC0) == 0x40) {
531  offs += 2;
532  len -= 2;
533  }
534  if (offs <= Length - 5 && len >= 5 && (Data[offs] & 0xF0) == 0x20) {
535  offs += 5;
536  len -= 5;
537  }
538  else if (offs <= Length - 10 && len >= 10 && (Data[offs] & 0xF0) == 0x30) {
539  offs += 10;
540  len -= 10;
541  }
542  else if (offs < Length && len > 0) {
543  offs++;
544  len--;
545  }
546  }
547  if (blen + len > Length) // invalid PES length field
548  break;
549  memcpy(&buf[blen], &Data[offs], len);
550  i = offs + len;
551  blen += len;
552  }
553  else if (Data[i + 3] >= 0xBD && Data[i + 3] <= 0xDF) // other PES packets
554  i += len + 6;
555  else
556  i++;
557  }
558  else
559  i++;
560  }
561  mHdffCmdIf->CmdAvShowStillImage(0, (uint8_t *)buf, blen, MapVideoStreamTypes(PatPmtParser()->Vtype()));
562  free(buf);
563  }
564  else {
565  // non-PES data
566  mHdffCmdIf->CmdAvShowStillImage(0, Data, Length, MapVideoStreamTypes(PatPmtParser()->Vtype()));
567  }
568 }
569 
570 bool cDvbHdFfDevice::Poll(cPoller &Poller, int TimeoutMs)
571 {
572  Poller.Add(fd_video, true);
573  return Poller.Poll(TimeoutMs);
574 }
575 
576 bool cDvbHdFfDevice::Flush(int TimeoutMs)
577 {
578  //TODO actually this function should wait until all buffered data has been processed by the card, but how?
579  return true;
580 }
581 
582 void cDvbHdFfDevice::BuildTsPacket(uint8_t * TsBuffer, bool PusiSet, uint16_t Pid, uint8_t Counter, const uint8_t * Data, uint32_t Length)
583 {
584  TsBuffer[0] = 0x47;
585  TsBuffer[1] = PusiSet ? 0x40 : 0x00;
586  TsBuffer[1] |= Pid >> 8;
587  TsBuffer[2] = Pid & 0xFF;
588  if (Length >= 184)
589  {
590  TsBuffer[3] = 0x10 | Counter;
591  memcpy(TsBuffer + 4, Data, 184);
592  }
593  else
594  {
595  uint8_t adaptationLength;
596 
597  TsBuffer[3] = 0x30 | Counter;
598  adaptationLength = 183 - Length;
599  TsBuffer[4] = adaptationLength;
600  if (adaptationLength > 0)
601  {
602  TsBuffer[5] = 0x00;
603  memset(TsBuffer + 6, 0xFF, adaptationLength - 1);
604  }
605  memcpy(TsBuffer + 5 + adaptationLength, Data, Length);
606  }
607 }
608 
609 uint32_t cDvbHdFfDevice::PesToTs(uint8_t * TsBuffer, uint16_t Pid, uint8_t & Counter, const uint8_t * Data, uint32_t Length)
610 {
611  uint32_t tsOffset;
612  uint32_t i;
613 
614  tsOffset = 0;
615  i = 0;
616  while (Length > 0)
617  {
618  BuildTsPacket(TsBuffer + tsOffset, i == 0, Pid, Counter, Data + i * 184, Length);
619  if (Length >= 184)
620  Length -= 184;
621  else
622  Length = 0;
623  Counter = (Counter + 1) & 15;
624  tsOffset += 188;
625  i++;
626  }
627  return tsOffset;
628 }
629 
630 int cDvbHdFfDevice::PlayVideo(const uchar *Data, int Length)
631 {
632  if (freezed)
633  return -1;
634  //TODO: support greater Length
635  uint8_t tsBuffer[188 * 16];
636  uint32_t tsLength;
637  int pid = 100;
638 
639  tsLength = PesToTs(tsBuffer, pid, videoCounter, Data, Length);
640 
641  if (pid != playVideoPid) {
642  playVideoPid = pid;
644  }
645  if (WriteAllOrNothing(fd_video, tsBuffer, tsLength, 1000, 10) <= 0)
646  Length = 0;
647  return Length;
648 }
649 
650 int cDvbHdFfDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
651 {
652  if (freezed)
653  return -1;
654  if (trickMode)
655  return Length;
656  uint8_t streamId;
657  uint8_t tsBuffer[188 * 16];
658  uint32_t tsLength;
661  int pid;
662 
663  streamId = Data[3];
664  if (streamId >= 0xC0 && streamId <= 0xDF)
665  {
666  streamType = HDFF_AUDIO_STREAM_MPEG1;
667  }
668  else if (streamId == 0xBD)
669  {
670  const uint8_t * payload = Data + 9 + Data[8];
671  if ((payload[0] & 0xF8) == 0xA0)
672  {
673  containerType = HDFF_AV_CONTAINER_PES_DVD;
674  streamType = HDFF_AUDIO_STREAM_PCM;
675  }
676  else if ((payload[0] & 0xF8) == 0x88)
677  {
678  containerType = HDFF_AV_CONTAINER_PES_DVD;
679  streamType = HDFF_AUDIO_STREAM_DTS;
680  }
681  else if ((payload[0] & 0xF8) == 0x80)
682  {
683  containerType = HDFF_AV_CONTAINER_PES_DVD;
684  streamType = HDFF_AUDIO_STREAM_AC3;
685  }
686  else
687  {
688  streamType = HDFF_AUDIO_STREAM_AC3;
689  }
690  }
691  pid = 200 + (int) streamType;
692  tsLength = PesToTs(tsBuffer, pid, audioCounter, Data, Length);
693 
694  if (pid != playAudioPid) {
695  playAudioPid = pid;
696  mHdffCmdIf->CmdAvSetAudioPid(0, playAudioPid, streamType, containerType);
697  }
698  if (WriteAllOrNothing(fd_video, tsBuffer, tsLength, 1000, 10) <= 0)
699  Length = 0;
700  return Length;
701 }
702 
703 int cDvbHdFfDevice::PlayTsVideo(const uchar *Data, int Length)
704 {
705  if (freezed)
706  return -1;
707  int pid = TsPid(Data);
708  if (pid != playVideoPid) {
709  PatPmtParser();
710  if (pid == PatPmtParser()->Vpid()) {
711  playVideoPid = pid;
713  }
714  }
715  return WriteAllOrNothing(fd_video, Data, Length, 1000, 10);
716 }
717 
719 {
720  switch (Atype) {
721  case 0x03: return HDFF_AUDIO_STREAM_MPEG1;
722  case 0x04: return HDFF_AUDIO_STREAM_MPEG2;
725  case 0x0F: return HDFF_AUDIO_STREAM_AAC;
726  case 0x11: return HDFF_AUDIO_STREAM_HE_AAC;
727  default: return HDFF_AUDIO_STREAM_MPEG1;
728  }
729 }
730 
731 int cDvbHdFfDevice::PlayTsAudio(const uchar *Data, int Length)
732 {
733  if (freezed)
734  return -1;
735  if (trickMode)
736  return Length;
737  int pid = TsPid(Data);
738  if (pid != playAudioPid) {
739  playAudioPid = pid;
740  int AudioStreamType = -1;
741  for (int i = 0; PatPmtParser()->Apid(i); i++) {
742  if (playAudioPid == PatPmtParser()->Apid(i)) {
743  AudioStreamType = PatPmtParser()->Atype(i);
744  break;
745  }
746  }
747  if (AudioStreamType < 0) {
748  for (int i = 0; PatPmtParser()->Dpid(i); i++) {
749  if (playAudioPid == PatPmtParser()->Dpid(i)) {
750  AudioStreamType = PatPmtParser()->Dtype(i);
751  break;
752  }
753  }
754  }
756  }
757  return WriteAllOrNothing(fd_video, Data, Length, 1000, 10);
758 }
759 
761 {
762  //TODO why not just keep a pointer?
763  if (devHdffOffset >= 0) {
765  if (device)
766  return device->mHdffCmdIf;
767  }
768  return NULL;
769 }
770 
771 // --- cDvbHdFfDeviceProbe ---------------------------------------------------
772 
773 bool cDvbHdFfDeviceProbe::Probe(int Adapter, int Frontend)
774 {
775  static uint32_t SubsystemIds[] = {
776  0x13C23009, // Technotrend S2-6400 HDFF development samples
777  0x13C2300A, // Technotrend S2-6400 HDFF production version
778  0x00000000
779  };
780  cString FileName;
781  cReadLine ReadLine;
782  FILE *f = NULL;
783  uint32_t SubsystemId = 0;
784  FileName = cString::sprintf("/sys/class/dvb/dvb%d.frontend%d/device/subsystem_vendor", Adapter, Frontend);
785  if ((f = fopen(FileName, "r")) != NULL) {
786  if (char *s = ReadLine.Read(f))
787  SubsystemId = strtoul(s, NULL, 0) << 16;
788  fclose(f);
789  }
790  FileName = cString::sprintf("/sys/class/dvb/dvb%d.frontend%d/device/subsystem_device", Adapter, Frontend);
791  if ((f = fopen(FileName, "r")) != NULL) {
792  if (char *s = ReadLine.Read(f))
793  SubsystemId |= strtoul(s, NULL, 0);
794  fclose(f);
795  }
796  for (uint32_t *sid = SubsystemIds; *sid; sid++) {
797  if (*sid == SubsystemId) {
798  FileName = cString::sprintf("/dev/dvb/adapter%d/osd0", Adapter);
799  int fd = open(FileName, O_RDWR);
800  if (fd != -1) { //TODO treat the second path of the S2-6400 as a budget device
801  close(fd);
802  dsyslog("creating cDvbHdFfDevice");
803  new cDvbHdFfDevice(Adapter, Frontend);
804  return true;
805  }
806  }
807  }
808  return false;
809 }